In this document we present the joint analysis of the PASS1A metabolomics datasets.

Load all datasets

Load the data from the cloud, including: phenotypic data, metabolomic datasets, and metabolomics dictionary.

source("~/Desktop/repos/motrpac-bic-norm-qc/tools/supervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/unsupervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/gcp_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/association_analysis_methods.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/data_aux_functions.R")
source("~/Desktop/repos/motrpac/tools/prediction_ml_tools.R")
library(randomForest) # for classification tests
# Load the dmaqc data
merged_dmaqc_data =  load_from_bucket("merged_dmaqc_data2019-10-15.RData",
    "gs://bic_data_analysis/pass1a/pheno_dmaqc/",F)
merged_dmaqc_data = merged_dmaqc_data[[1]]
rownames(merged_dmaqc_data) = as.character(merged_dmaqc_data$vial_label)
# define the tissue variable
merged_dmaqc_data$tissue = merged_dmaqc_data$sampletypedescription
# define the time to freeze variable
merged_dmaqc_data$time_to_freeze = merged_dmaqc_data$calculated.variables.time_death_to_collect_min + 
  merged_dmaqc_data$calculated.variables.time_collect_to_freeze_min
# col time vs. control
# df = data.frame(
#   bid = merged_dmaqc_data$bid,
#   edta_col_time = merged_dmaqc_data$calculated.variables.edta_coll_time,
#   time_to_freeze = merged_dmaqc_data$time_to_freeze,
#   is_control = merged_dmaqc_data$animal.key.is_control,
#   tp = merged_dmaqc_data$animal.key.timepoint,
#   tissue = merged_dmaqc_data$specimen.processing.sampletypedescription
# )
# df = unique(df)
# boxplot(edta_col_time/3600 ~ is_control,df)
# boxplot(edta_col_time/3600 - tp ~ is_control,df)
# wilcox.test(edta_col_time/3600 ~ is_control,df)
# blood freeze times
blood_samples = 
  merged_dmaqc_data$specimen.processing.sampletypedescription ==
  "EDTA Plasma"
blood_freeze_time = 
  as.difftime(merged_dmaqc_data$specimen.processing.t_freeze,units = "mins") -
  as.difftime(merged_dmaqc_data$specimen.processing.t_edtaspin,units="mins")
blood_freeze_time = as.numeric(blood_freeze_time)
time_to_freeze = merged_dmaqc_data$time_to_freeze[blood_samples] = 
  blood_freeze_time[blood_samples]
# Load our parsed metabolomics datasets
metabolomics_bucket_obj = load_from_bucket(
  file = "metabolomics_parsed_datasets_pass1a_external1.RData",
  bucket = "gs://bic_data_analysis/pass1a/metabolomics/")
metabolomics_parsed_datasets = metabolomics_bucket_obj$metabolomics_parsed_datasets

Define the variables to be adjusted for:

biospec_cols = c(
  "acute.test.distance",
  "calculated.variables.time_to_freeze",
  # "calculated.variables.edta_coll_time", # no need - see code above for blood
  "bid" # required for matching datasets
  )
differential_analysis_cols = c(
  "animal.registration.sex",
  "animal.key.timepoint",
  "animal.key.is_control"
)
pipeline_qc_cols = c("sample_order")

Log-transform: effect on variance

Some sites do not use the log transformation on their dataset. In this section we plot the coefficient of variation as a function of the mean instensity. We take a single dataset as an example to show how log-transformed data have reduced dependency and smoother plots.

As an additional analysis we also plot the number of missing values per metabolite as a function of its mean intensity. We show that while there is high correlation some missing values appear in fairely high intensities. This is important for imputation as some sites use some fixed low value instead of knn imputation.


# Plot cv vs means
library(gplots)
d = metabolomics_parsed_datasets[["white_adipose_powder,metab_u_hilicpos,unnamed"]]
dx = d$sample_data
CoV<-function(x){return(sd(x,na.rm = T)/mean(x,na.rm=T))}
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Raw data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Repeat after log2
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Log2 data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Plot number of NAs vs intensity mean
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
num_nas = rowSums(is.na(dx))
df = data.frame(Num_NAs = num_nas[inds],Mean_intensity = dmeans[inds])
rho = cor(df$Num_NAs,df$Mean_intensity,method="spearman")
rho = format(rho,digits=2)
plot(Num_NAs~Mean_intensity,df,cex=0.5,pch=20,
     main=paste("Spearman:",rho))

Load the preprocessed and normalized data

metabolomics_processed_datasets = load_from_bucket(
  file="metabolomics_processed_datasets11182019.RData",
  bucket = "gs://bic_data_analysis/pass1a/metabolomics/"
)[[1]]
Copying gs://bic_data_analysis/pass1a/metabolomics/metabolomics_processed_datasets11182019.RData...
/ [0 files][    0.0 B/480.7 MiB]                                                
-
- [0 files][    0.0 B/480.7 MiB]                                                
\
\ [0 files][ 17.5 MiB/480.7 MiB]                                                
|
/
/ [0 files][ 37.9 MiB/480.7 MiB]                                                
-
\
\ [0 files][ 63.4 MiB/480.7 MiB]                                                
|
| [0 files][ 89.7 MiB/480.7 MiB]                                                
/
-
- [0 files][116.0 MiB/480.7 MiB]                                                
\
|
| [0 files][140.0 MiB/480.7 MiB]                                                
/
/ [0 files][164.7 MiB/480.7 MiB]                                                
-
\
\ [0 files][189.2 MiB/480.7 MiB]                                                
|
/
/ [0 files][212.2 MiB/480.7 MiB]                                                
-
- [0 files][229.9 MiB/480.7 MiB]   21.8 MiB/s                                   
\
|
| [0 files][252.5 MiB/480.7 MiB]   21.6 MiB/s                                   
/
-
- [0 files][277.8 MiB/480.7 MiB]   21.6 MiB/s                                   
\
\ [0 files][302.8 MiB/480.7 MiB]   21.6 MiB/s                                   
|
/
/ [0 files][330.1 MiB/480.7 MiB]   22.6 MiB/s                                   
-
\
\ [0 files][355.4 MiB/480.7 MiB]   25.0 MiB/s                                   
|
| [0 files][371.3 MiB/480.7 MiB]   23.5 MiB/s                                   
/
-
- [0 files][387.3 MiB/480.7 MiB]   21.6 MiB/s                                   
\
|
| [0 files][409.9 MiB/480.7 MiB]   21.1 MiB/s                                   
/
/ [0 files][434.4 MiB/480.7 MiB]   20.5 MiB/s                                   
-
\
\ [0 files][453.3 MiB/480.7 MiB]   19.2 MiB/s                                   
|
/
/ [0 files][475.2 MiB/480.7 MiB]   20.8 MiB/s                                   
/ [1 files][480.7 MiB/480.7 MiB]   21.4 MiB/s                                   

Operation completed over 1 objects/480.7 MiB.                                    
# Reduce the metadata to the selected columns
for(currname in names(metabolomics_processed_datasets)){
  curr_data = metabolomics_processed_datasets[[currname]]$normalized_data[[1]]
  # organize the metadata
  curr_meta = merged_dmaqc_data[colnames(curr_data),
        union(biospec_cols,differential_analysis_cols)]
  # remove metadata variables with too many NAs
  na_counts = apply(is.na(curr_meta),2,sum)
  curr_meta = curr_meta[,na_counts/nrow(curr_meta) < 0.1]
  metabolomics_processed_datasets[[currname]]$sample_meta_parsed = curr_meta
}

Log-transform: effect on differential analysis in targeted data

Untargeted data are typically log-transformed and analyzed using linear models. On the other hand, concentration data are sometimes analyzed with the same type of models but using the original data. This raises a problem if we wish to compare exact statistics from these data. In this section we perform residual analysis for single metabolites. Our goal is to identify if concentration data behaves “normally” when not log-transformed. The analysis below examines the residuals of the data after fitting linear models for each metabolite, adjusting for freeze time and sex. We then compare the results with and without the log-transformation, counting the number of metabolites with a significant evidence for non-normally distributed residuals.

# check for normality using the Kolmogorov-Smirnov test
is_normal_test<-function(v){
  if(sd(v,na.rm = T)==0){return(0)}
  try({return(shapiro.test(v)$p.value)})
  # The Shapiro test may fail if the sd of v is zero
  return(ks.test(v,"pnorm",mean(v,na.rm=T),sd(v,na.rm = T))$p.value)
}
# go over the named datasets, get a logged and an unlogged version of
# the data, use these as inputs for the regression
residual_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  if(!metabolomics_processed_datasets[[nn1]]$is_targeted){next}
  x_log = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
  x_unlog = 2^x_log
  
  # take the covariates, ignore distances
  x_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
  curr_covs = x_meta[,intersect(colnames(x_meta),biospec_cols[2])]
  curr_covs = data.frame(curr_covs,
           sex=x_meta$animal.registration.sex)
  
  # get the lm objects
  curr_models = list()
  for(tp in unique(x_meta$animal.key.timepoint)){
      res_log = apply(
        x_log,1,
        pass1a_simple_differential_abundance,
        tps = x_meta$animal.key.timepoint,tp=tp,
        is_control = x_meta$animal.key.is_control,
        covs = curr_covs,return_model=T
      )
      res_unlog = apply(
        x_unlog,1,
        pass1a_simple_differential_abundance,
        tps = x_meta$animal.key.timepoint,tp=tp,
        is_control = x_meta$animal.key.is_control,
        covs = curr_covs,return_model=T
      )
      is_norm = cbind(
        sapply(res_log,function(x)is_normal_test(residuals(x))),
        sapply(res_unlog,function(x)is_normal_test(residuals(x)))
      )
      colnames(is_norm) = c("log","not log")
      curr_models[[as.character(tp)]] = is_norm
      
      # # test a specific model
      # ind = 246
      # plot(x_unlog[ind,],x_log[ind,])
      # 
      # resids_log = rstandard(res_log[[ind]])
      # fit_log = res_log[[ind]]$fitted.values
      # resids_unlog = rstandard(res_unlog[[ind]])
      # fit_unlog = res_unlog[[ind]]$fitted.values
      # 
      # plot(fit_log,resids_log)
      # plot(fit_unlog,resids_unlog)
  }
  residual_analysis_results[[nn1]] = curr_models
}

# Is there a significant difference between the two options?
log_vs_unlog_summ_mat = sapply(residual_analysis_results,
    function(x)sapply(x,
        function(y)
          wilcox.test(y[,1],y[,2],paired = T,alternative = "g")$p.value))

# Count the number of non-normal metabolites
num_nonnormal_log = sapply(residual_analysis_results,
    function(x)sapply(x,
        function(y)sum(y[,1]<0.05)))
num_nonnormal_log = 
  num_nonnormal_log[,order(colnames(num_nonnormal_log))]
num_nonnormal_unlog = sapply(residual_analysis_results,
    function(x)sapply(x,
        function(y)sum(y[,2]<0.05)))
num_nonnormal_unlog = 
  num_nonnormal_unlog[,order(colnames(num_nonnormal_unlog))]

library(corrplot)
par(mar = c(5,5,5,10))
normdiffs = t(num_nonnormal_log)- t(num_nonnormal_unlog)
corrplot(normdiffs,is.corr = F,tl.cex = 0.7)

Trageted vs. Untargeted: single metabolite comparison

Compute statistics for each dataset

Compare overlaps, effect sizes, and correlations within tissues. Compare targeted-untargeted pairs only. For differential analysis we use the same model as in the analysis above.

# Transform the data matrix to have compound names as row names.
# This requires removing rows without names and changing the rownames of
# the input matrix x.
# Also, do not assume that the row annotation orde fits the data necessarily
extract_by_comp_name_from_row_annot<-function(x,row_annot_x){
  # get the column that has the row names of x or at least
  # have the greatest intersection with it
  int_sizes = apply(row_annot_x,2,function(x,y)length(intersect(x,y)),y=rownames(x))
  ind = which(int_sizes==max(int_sizes,na.rm = T))[1]
  # update the annotation table to have only rows that have 
  # a row in x and then update the rownames to be from the 
  # selected column
  row_annot_x = row_annot_x[is.element(row_annot_x[,ind],set=rownames(x)),]
  rownames(row_annot_x) = row_annot_x[,ind]
  # we can now intersect x and the annotation using their row names
  # and update x accordingly
  shared = intersect(rownames(row_annot_x),rownames(x))
  x = x[shared,]
  row_annot_x = row_annot_x[shared,]
  rownames(x) = row_annot_x$motrpac_comp_name
  return(x)
}
tar_vs_untar_norm_pairs = list(
  c("log2,imp","log2,imp,TMM"),
  c("log2,imp","log2,imp"),
  c("log2,imp","med,log,imp"),
  c("imp,none","imp,none"),
  c("imp,none","imp,TMM"),
  c("imp,none","med,log,imp")
)
# The loop below is the core of the computations for comparing 
# targeted and untargeted data when using known compound names
#
# For each normalization option (a pair from the list above) we compute
# all pairwise correlation matrices of the overlapping metabolites and
# the differential analysis results (again of the overlap).
# These objects are then used later for other quantitative comparisons
norm_method2comparison_results = list()
for(norm_pairs in tar_vs_untar_norm_pairs){
  tar_norm_method = norm_pairs[1]
  untar_norm_method = norm_pairs[2]
  single_metabolite_corrs = list()
  single_metabolite_de = c()
  named2covered_shared_metabolites = list()
  for(nn1 in names(metabolomics_processed_datasets)){
    nn1_tissue = metabolomics_processed_datasets[[nn1]]$tissue
    if(!metabolomics_processed_datasets[[nn1]]$is_targeted){next}
    single_metabolite_corrs[[nn1]] = list()
    named2covered_shared_metabolites[[nn1]] = NULL
    for(nn2 in names(metabolomics_processed_datasets)){
      if(nn2 == nn1){next}
      if(metabolomics_processed_datasets[[nn2]]$is_targeted){next}
      nn2_tissue = metabolomics_processed_datasets[[nn2]]$tissue
      nn2_dataset = paste(strsplit(nn2,split=",")[[1]][3:4],collapse = ",")
      if(nn1_tissue!=nn2_tissue){next}
      # get the numeric datasets and their annotation
      x = metabolomics_processed_datasets[[nn1]]$normalized_data[[tar_norm_method]]
      y = metabolomics_processed_datasets[[nn2]]$normalized_data[[untar_norm_method]]
      row_annot_x = metabolomics_processed_datasets[[nn1]]$row_annot
      row_annot_y = metabolomics_processed_datasets[[nn2]]$row_annot
      # transform metabolite names to the motrpac comp name
      x = extract_by_comp_name_from_row_annot(x,row_annot_x)
      y = extract_by_comp_name_from_row_annot(y,row_annot_y)
      # align the sample sets
      bid_y = merged_dmaqc_data[colnames(y),"bid"]
      bid_x = merged_dmaqc_data[colnames(x),"bid"]    
      # step 1: merge samples from the same BID
      if(length(unique(bid_x))!=length(bid_x)){
        x = aggregate_repeated_samples(x,bid_x)
      }
      else{
        colnames(x) = bid_x
      }
      if(length(unique(bid_y))!=length(bid_y)){
        y = aggregate_repeated_samples(y,bid_y)
      }else{
        colnames(y) = bid_y
      }
      # step 2: use the shared bio ids
      shared_bids = as.character(intersect(colnames(y),colnames(x)))
      x = as.matrix(x[,shared_bids])
      y = as.matrix(y[,shared_bids])
      # At this point x and y are over the same BIDs, now we add the metadata
      y_meta = 
        unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
      rownames(y_meta) = y_meta$bid
      y_meta = y_meta[shared_bids,]
      
      # If one dataset is log-transformed and the other is not
      # transform back to the original values
      is_tar_log = grepl("log",tar_norm_method)
      is_untar_log = grepl("log",untar_norm_method)
      if(is_tar_log && !is_untar_log){
        x = 2^x
      }
      if(!is_tar_log && is_untar_log){
        y = 2^y
      }
    
      # If data are not log-transformed then scale the rows
      if(!is_tar_log || !is_untar_log){
        x = t(scale(t(x),center = T,scale = T))
        y = t(scale(t(y),center = T,scale = T))
      }
    
      # get the shared matebolites
      shared_metabolites = intersect(rownames(x),rownames(y))
      shared_metabolites = na.omit(shared_metabolites)
      if(length(shared_metabolites)==0){next}
      named2covered_shared_metabolites[[nn1]] = union(
        named2covered_shared_metabolites[[nn1]],
        shared_metabolites
      )
    
      # Compute the correlation matrices of the shared metabolites
      if(length(shared_metabolites)>1){
          corrs =cor(t(x[shared_metabolites,]),
                t(y[shared_metabolites,]),method = "spearman")
      }
      else{
          corrs = cor(x[shared_metabolites,],
                y[shared_metabolites,],method = "spearman")
      }
    
      # take the covariates (ignore distances)
      curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
      curr_covs = data.frame(y_meta[,curr_cov_cols])
      names(curr_covs) = curr_cov_cols
      curr_covs$sex = y_meta$animal.registration.sex # add sex
    
      # differential analysis
      for(tp in unique(y_meta$animal.key.timepoint)){
        curr_control_tp = NULL
        # if(tp == 7 || tp == 4){curr_control_tp=7}
        resx = t(apply(
          matrix(x[shared_metabolites,],nrow=length(shared_metabolites)),1,
          pass1a_simple_differential_abundance,
          tps = y_meta$animal.key.timepoint,tp=tp,
          is_control = y_meta$animal.key.is_control,
          covs = curr_covs,return_model=F,
          control_tp = curr_control_tp
        ))
        resy = t(apply(
          matrix(y[shared_metabolites,],nrow=length(shared_metabolites)),1,
          pass1a_simple_differential_abundance,
          tps = y_meta$animal.key.timepoint,tp=tp,
          is_control = y_meta$animal.key.is_control,
          covs = curr_covs,return_model=F,
          control_tp = curr_control_tp
        ))
        # Add dataset information, time point, tissue
        # These are important annotations for our summary matrix
        # called single_metabolite_de below
        added_columns = matrix(cbind(
          rep(nn1,length(shared_metabolites)),
          rep(nn2,length(shared_metabolites)),
          shared_metabolites,
          rep(tp,length(shared_metabolites)),
          rep(nn1_tissue,length(shared_metabolites))
        ),nrow=length(shared_metabolites))
        resx = cbind(resx,rep(T,nrow(resx)))
        colnames(resx)[ncol(resx)] = "is_targeted"
        resy = cbind(resy,rep(F,nrow(resy)))
        colnames(resy)[ncol(resy)] = "is_targeted"
        if(nrow(resx)>1){
          resx = cbind(added_columns[,-2],resx)
          resy = cbind(added_columns[,-1],resy)
        }
        else{
          resx = c(added_columns[,-2],resx)
          resy = c(added_columns[,-1],resy)
       }
        single_metabolite_de = rbind(single_metabolite_de,resx)
        single_metabolite_de = rbind(single_metabolite_de,resy)
      }
      single_metabolite_corrs[[nn1]][[nn2]] = corrs
    }
  }
  # Reformat the differential analysis results for easier comparison later
  single_metabolite_de = data.frame(single_metabolite_de)
  names(single_metabolite_de) = c("dataset","metabolite","tp","tissue",
    "Est","Std","Tstat","Pvalue","is_targeted")
  for(col in names(single_metabolite_de)[-c(1:4)]){
    single_metabolite_de[[col]] = as.numeric(
      as.character(single_metabolite_de[[col]]))
  }
  for(col in names(single_metabolite_de)[1:4]){
    single_metabolite_de[[col]] = 
      as.character(single_metabolite_de[[col]])
  }
  # Remove duplications
  # Rounding the p-values - necessary for removing duplicates
  # using the unique function
  rownames(single_metabolite_de) = NULL
  for(nn in names(single_metabolite_de)){
    ndig = 5
    if(grepl("pval",nn,ignore.case = T)){
      ndig = 10
    }
    if(is.numeric(single_metabolite_de[[nn]])){
      single_metabolite_de[[nn]] = 
      round(single_metabolite_de[[nn]],digits = ndig)
    }
  }
  single_metabolite_de = unique(single_metabolite_de)
  
  # Finally, store all the results for the current normalization pair
  pair_name = paste(tar_norm_method,untar_norm_method,sep=";")
  norm_method2comparison_results[[pair_name]] = list(
    single_metabolite_de = single_metabolite_de,
    single_metabolite_corrs = single_metabolite_corrs,
    named2covered_shared_metabolites = named2covered_shared_metabolites
  )
}

We next transform the data above into tables that contain data for each combination of metabolite, time point, and tissue. These are then used for different meta-analyses: (1) a simple random effects analysis, (2) random effects with a binary covariate indicating if a dataset is targeted or untargeted, (3) redo the RE model of (1) with the targeted data only, and (4) redo the RE model of (1) with the untargeted data only.

library(metafor)
for(pair_name in names(norm_method2comparison_results)){
  meta_analysis_stats = list()
  single_metabolite_de = 
    norm_method2comparison_results[[pair_name]]$single_metabolite_de
  for(tissue in unique(single_metabolite_de$tissue)){
    for(tp in unique(single_metabolite_de$tp)){
      curr_subset = single_metabolite_de[
        single_metabolite_de$tissue==tissue &
          single_metabolite_de$tp==tp,]
      for(metabolite in unique(curr_subset$metabolite)){
        curr_met_data = curr_subset[
          curr_subset$metabolite==metabolite,]
        curr_met_data$var = curr_met_data$Std^2
        re_model1 = NULL;re_model2=NULL
        re_model_tar = NULL;re_model_untar = NULL
        try({re_model1 = rma.uni(curr_met_data$Est,curr_met_data$var,method="FE")})
        try({re_model2 = rma.mv(curr_met_data$Est,curr_met_data$var,
                           mods=curr_met_data$is_targeted,method="FE")})
        try({re_model_tar = rma.uni(
          curr_met_data[curr_met_data$is_targeted==1,"Est"],
          curr_met_data[curr_met_data$is_targeted==1,"var"],
          method="FE"
        )})
        try({re_model_untar = rma.uni(
          curr_met_data[curr_met_data$is_targeted==0,"Est"],
          curr_met_data[curr_met_data$is_targeted==0,"var"],
          method="FE"
        )})
        meta_analysis_stats[[paste(metabolite,tissue,tp,sep=",")]] = 
          list(curr_met_data=curr_met_data,re_model1=re_model1,
            re_model2 = re_model2,re_model_tar=re_model_tar,
            re_model_untar = re_model_untar)
      }
    }
  }
  norm_method2comparison_results[[pair_name]]$meta_analysis_stats =
    meta_analysis_stats
}

Targeted vs. Untargeted: compound overlap

We first plot the number and percentage of metabolites in the targeted datasets that are measured in at least one untargeted dataset.

library(ggplot2)
dataset2num_metabolites = sapply(metabolomics_processed_datasets, 
                                 function(x)nrow(x$sample_data))
named_dataset_coverage = sapply(named2covered_shared_metabolites,length)
named_dataset_coverage = data.frame(
  name = names(named_dataset_coverage),
  percentage = named_dataset_coverage /
  dataset2num_metabolites[names(named_dataset_coverage)],
  count = named_dataset_coverage,
  total = dataset2num_metabolites[names(named_dataset_coverage)]
)
# add datasets with no coverage
all_targeted_datasets = names(metabolomics_processed_datasets)
all_targeted_datasets = all_targeted_datasets[!grepl("untar",all_targeted_datasets)]
zero_coverage_datasets = setdiff(all_targeted_datasets,
                                 named_dataset_coverage$name)
zero_coverage_datasets = data.frame(
  name = zero_coverage_datasets,
  percentage = rep(0,length(zero_coverage_datasets)),
  count = rep(0,length(zero_coverage_datasets)),
  total = dataset2num_metabolites[zero_coverage_datasets]
)
named_dataset_coverage = rbind(named_dataset_coverage,
                           zero_coverage_datasets)
named_dataset_coverage = 
  named_dataset_coverage[order(as.character(named_dataset_coverage$name)),]
print(ggplot(named_dataset_coverage, aes(x=name, y=percentage)) + 
  geom_bar(stat = "identity",width=0.2) + coord_flip() +
  geom_text(data=named_dataset_coverage, 
            aes(name, percentage+0.05, label=count), 
            position = position_dodge(width=0.9),
            size=4) + 
  ggtitle("Targeted dataset: coverage by untargeted"))

Comparison results: normalization methods

Spearman correlations

Compare the normalization methods by their correlation distributions.


rep_correlations = c()
tissues = unique(sapply(metabolomics_processed_datasets,function(x)x$tissue))
for(pair_name in names(norm_method2comparison_results)){
  single_metabolite_corrs = 
    norm_method2comparison_results[[pair_name]]$single_metabolite_corrs
  for(tissue in tissues){
    curr_datasets = names(single_metabolite_corrs)[
      grepl(tissue,names(single_metabolite_corrs))
    ]
    between_corrs = c()
    for(tar_dataset in curr_datasets){
      l = single_metabolite_corrs[[tar_dataset]]
      between_corrs = c(between_corrs,unname(unlist(sapply(l,diag))))
    }
    rep_correlations = rbind(rep_correlations,
          c(pair_name,tissue,mean(between_corrs,na.rm=T),sd(between_corrs,na.rm=T)))
  }
}
rep_correlations = rep_correlations[!is.na(rep_correlations[,3]),]
rep_correlations = data.frame(
  "NormMethod" = rep_correlations[,1],
  "Tissue" = rep_correlations[,2],
  "Mean" = as.numeric(rep_correlations[,3]),
  "SD" = as.numeric(rep_correlations[,4])
)
rep_correlations = rep_correlations[order(rep_correlations$Tissue),]
print(
    ggplot(rep_correlations, aes(x=Tissue, y=Mean, fill=NormMethod)) +
      geom_bar(position=position_dodge(), stat="identity", colour='black') +
      geom_errorbar(aes(ymin=Mean-SD, ymax=Mean+SD),na.rm=T, 
                   width=.2,position=position_dodge(.9))
)

Differential analysis results

pthr = 0.001
for(tissue in tissues){
  for(pair_name in names(norm_method2comparison_results)){
      meta_analysis_stats = 
          norm_method2comparison_results[[pair_name]]$meta_analysis_stats
      meta_analysis_stats = meta_analysis_stats[
        grepl(tissue,names(meta_analysis_stats))
      ]
      if(length(meta_analysis_stats)==0){next}
      naive_analysis_tar = sapply(meta_analysis_stats,
        function(x)sum(x$curr_met_data[,"Pvalue"]<pthr &
                     x$curr_met_data[,"is_targeted"]))
      naive_analysis_untar = sapply(meta_analysis_stats,
        function(x)sum(x$curr_met_data[,"Pvalue"]<pthr &
                     !x$curr_met_data[,"is_targeted"]))
      tb = table(naive_analysis_tar,naive_analysis_untar)
      nonsig_in_both = tb[1,1]
      tb[1,1] = 0
      barplot(t(tb),
              legend=T,xlab="Number of targeted datasets with p<0.001",
              ylab = "log2 number of metabolites",
              main = paste(tissue,pair_name))
  }
}

Meta-analysis results

library(VennDiagram)
require(gridExtra)
pthr = 0.001
tissues = unique(sapply(metabolomics_processed_datasets,function(x)x$tissue))
tar_vs_untar_correlations = c()
for(tissue in tissues){
  for(pair_name in names(norm_method2comparison_results)){
    meta_analysis_stats = 
        norm_method2comparison_results[[pair_name]]$meta_analysis_stats
    meta_analysis_stats  = meta_analysis_stats[
        grepl(tissue,names(meta_analysis_stats))
      ]
    if(is.null(meta_analysis_stats) || length(meta_analysis_stats)==0){next}
  
      # P-value for the difference between targeted and untargeted
    targeted_diff_p = 
    sapply(meta_analysis_stats,function(x)x$re_model2$pval[2])
   # P-values - targeted vs. untargeted
    pvals_tar = sapply(meta_analysis_stats,function(x)x$re_model_tar$pval)
    pvals_untar = sapply(meta_analysis_stats,function(x)x$re_model_untar$pval)
    pvals_untar = unlist(pvals_untar[sapply(pvals_untar,length)>0])
    significant_in = rep("None",length(pvals_untar))
    significant_in[pvals_tar<pthr] = "Targeted"
    significant_in[pvals_untar<pthr] = "Untargeted"
    significant_in[pvals_tar<pthr & pvals_untar<pthr] = "Both"
    significant_diff = targeted_diff_p<pthr
    rho = cor(-log10(pvals_tar),-log10(pvals_untar),method = "pearson")
    # Betas - targeted vs. untargeted
    betas_tar = 
      sapply(meta_analysis_stats,function(x)x$re_model_tar$beta[1,1])
    betas_untar = 
      sapply(meta_analysis_stats,function(x)x$re_model_untar$beta[1,1])
    betas_untar = unlist(betas_untar[sapply(betas_untar,length)>0])
    df = data.frame(
      targeted = betas_tar,
      untargeted = betas_untar,
      significant_in = significant_in,
      significant_diff = significant_diff
    )
    rho_beta = cor(betas_untar,betas_tar,method = "pearson")
    rhop = cor.test(betas_untar,betas_tar,method = "pearson")$p.value
    print(
      ggplot(df, aes(x=targeted, y=untargeted,
                 shape=significant_diff, color=significant_in)) +
      geom_point() + geom_abline(slope=1,intercept = 0) + 
      ggtitle(paste(tissue, pair_name,
                    "effects, rho=:",format(rho_beta,digits=2)))
    )
  
    tar_vs_untar_correlations = rbind(tar_vs_untar_correlations,
                                      c(tissue,pair_name,rho,rho_beta))
   # draw a venn diagram
    inter_area = sum(significant_in=="Both")
    tar_area = sum(significant_in=="Targeted") + inter_area
    untar_area = sum(significant_in=="Untargeted") + inter_area
    subt = paste("Not significant in both:",table(significant_in)["None"])
    
    venng = draw.pairwise.venn(tar_area,untar_area,inter_area,
            c("Targeted","Untargeted"),lty = rep("blank",2), 
            fill = c("pink", "cyan"), alpha = rep(0.5, 2),
            cat.dist = rep(0.01, 2),ind=F)
    # grid.newpage()
    grid.arrange(gTree(children=venng), 
                 top=paste(tissue,pair_name), bottom=subt)
  }
}

Comparison results: detailed analysis of the selected normalization

Spearman correlations

We examine the average correlation between the platforms (within tissues). Whenever two platforms share more than a single metabolite we plot both the average correlation between the same metabolites and between other metabolites. Adding the average correlation between platforms but with different metabolites is important as it gives some perspective to what a significant correlation is. That is, in many cases below, the average correlation may be greater than expected.

# Next examine the Spearman correlations between platforms
mean_abs<-function(x,...){return(mean(abs(x),...))}
sd_abs<-function(x,...){return(sd(abs(x),...))}
extract_diag_vs_non_diag<-function(corrs,func=mean,...){
  if(length(corrs)==1){
    return(c(same=func(corrs,...),other=NA))
  }
  same = func(diag(corrs),...)
  other = func(
    c(corrs[lower.tri(corrs,diag = F)]),...)
  return(c(same=same,other=other))
}
single_metabolite_corrs =
  norm_method2comparison_results$`log2,imp;log2,imp`$single_metabolite_corrs
for(tar_dataset in names(single_metabolite_corrs)){
  l = single_metabolite_corrs[[tar_dataset]]
  if(length(l)==0){next}
  corr_info = as.data.frame(t(sapply(l, extract_diag_vs_non_diag)))
  corr_sd = as.data.frame(t(sapply(l, extract_diag_vs_non_diag,func=sd)))
  # shorten the row names
  rownames(corr_info) = sapply(rownames(corr_info),
      function(x)paste(strsplit(x,split=",")[[1]][3:4],collapse=","))
  rownames(corr_sd) = rownames(corr_info)
  corr_info$dataset = rownames(corr_info)
  corr_sd$dataset = corr_info$dataset
  corr_info = melt(corr_info)
  corr_sd = melt(corr_sd)
  corr_info$sd = corr_sd$value
  print(
    ggplot(corr_info, aes(x=dataset, y=value, fill=variable)) +
      geom_bar(position=position_dodge(), stat="identity", colour='black') +
      geom_errorbar(aes(ymin=value-sd, ymax=value+sd),na.rm=T, 
                   width=.2,position=position_dodge(.9)) +
    ggtitle(tar_dataset) + xlab("Untargeted dataset") + ylab("Spearman") +
      labs(fill = "Pair type") + 
      theme(legend.position="top",legend.direction = "horizontal")
  )
}

Plot selected examples

Here are the results for lactate in plasma.

Example from a log2 dataset:

meta_analysis_stats = 
  norm_method2comparison_results$`log2,imp;med,log,imp`$meta_analysis_stats
lact_res = meta_analysis_stats[
  grepl("lact",names(meta_analysis_stats),ignore.case = T) &
    grepl("plasma",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
            function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
  curr_labels = gsub("plasma,","",
                     meta_analysis_stats[[lact_example]][[1]][,1])
  forest(meta_analysis_stats[[lact_example]]$re_model1,
       slab = curr_labels,
       main = lact_example,xlab = "Log fc",
       col = "blue",cex = 1.1)
}

Example from a scaled dataset:

meta_analysis_stats = 
  norm_method2comparison_results$`imp,none;imp,none`$meta_analysis_stats
lact_res = meta_analysis_stats[
  grepl("lact",names(meta_analysis_stats),ignore.case = T) &
    grepl("plasma",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
            function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
  curr_labels = gsub("plasma,","",
                     meta_analysis_stats[[lact_example]][[1]][,1])
  forest(meta_analysis_stats[[lact_example]]$re_model1,
       slab = curr_labels,
       main = lact_example,xlab = "Log fc",
       col = "blue",cex = 1.1)
}

We can now check the same analysis for liver:

lact_res = meta_analysis_stats[
  grepl("lact",names(meta_analysis_stats),ignore.case = T) &
    grepl("liver",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
            function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
  curr_labels = gsub("liver_powder,","",
                     meta_analysis_stats[[lact_example]][[1]][,1])
  forest(meta_analysis_stats[[lact_example]]$re_model1,
       slab = curr_labels,
       main = lact_example,xlab = "Log fc",
       col = "blue",cex = 1.1)
}

From the plots above we take the most extreme examples and examine their forest plots.

Targeted vs. untargeted: comparison as a prediction task

Use 5-fold cross validation for analysis within tissues. For each pair of targeted and untargeted datasets from the same tissue, we use the untargeted data as the predictive features and all metabolites in the targeted datasets as the dependent variables. The code below uses feature selection and random forests to train the predictive models.

nfolds = 5
prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  nn1_tissue = strsplit(nn1,split=",")[[1]][1]
  nn1_tissue = gsub("_powder","",nn1_tissue)
  if(grepl("untargeted",nn1)){next}
  for(nn2 in names(metabolomics_processed_datasets)){
    if(nn2 == nn1){next}
    if(!grepl("untargeted",nn2)){next}
    nn2_tissue = strsplit(nn2,split=",")[[1]][1]
    nn2_tissue = gsub("_powder","",nn2_tissue)
    nn2_dataset = strsplit(nn2,split=",")[[1]][2]
    if(nn1_tissue!=nn2_tissue){next}
    print(paste("features from:",nn2))
    print(paste("labels from:",nn1))
    # get the numeric datasets and their annotation
    y = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
    x = metabolomics_processed_datasets[[nn2]]$normalized_data[[1]]
    # align the sample sets
    bid_y = merged_dmaqc_data[colnames(y),"bid"]
    bid_x = merged_dmaqc_data[colnames(x),"bid"]    
    # step 1: merge samples from the same BID
    if(length(unique(bid_x))!=length(bid_x)){
      x = aggregate_repeated_samples(x,bid_x)
    }
    else{
      colnames(x) = bid_x
    }
    if(length(unique(bid_y))!=length(bid_y)){
      y = aggregate_repeated_samples(y,bid_y)
    }else{
      colnames(y) = bid_y
    }
    # step 2: use the shared bio ids
    shared_bids = as.character(intersect(colnames(y),colnames(x)))
    x = t(as.matrix(x[,shared_bids]))
    y = t(as.matrix(y[,shared_bids]))
    # At this point x and y are over the same BIDs, now we add the metadata
    y_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
    rownames(y_meta) = y_meta$bid
    y_meta = y_meta[shared_bids,]
    
    # take the covariates (ignore distances)
    curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
    curr_covs = data.frame(y_meta[,curr_cov_cols])
    names(curr_covs) = curr_cov_cols
    curr_covs$sex = y_meta$animal.registration.sex # add sex
    # add the covariates into x
    x = cbind(x,curr_covs)
    
    # Run the regressions
    folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
    numFeatures = min(ncol(x),2000)
    preds = c();real=c()
    for(i in 1:ncol(y)){
      if( i %% 10 == 0){print(paste("analyzing metabolite number:",i))}
      y_i = y[,1]
      i_preds = c();i_real=c()
      for(j in 1:nfolds){
        tr_x = x[folds!=j,]
        tr_yi = y_i[folds!=j]
        te_x = x[folds==j,]
        te_y = y_i[folds==j]
        # random forest
        # model = randomForest(tr_yi,x=tr_x,ntree = 20)
        # te_preds = predict(model,newdata = te_x)
        model = feature_selection_wrapper(tr_x,tr_yi,
                   coeff_of_var,randomForest,
                   topK = numFeatures,ntree=50)
        te_preds = predict(model,newdata = te_x)
        i_preds = c(i_preds,te_preds)
        i_real = c(i_real,te_y)
      }
      preds = cbind(preds,i_preds)
      real = cbind(real,i_real)
    }
    colnames(preds) = colnames(y)
    colnames(reals) = colnames(y)
    currname = paste(nn1,nn2,sep=";")
    prediction_analysis_results[[currname]] = list(
      preds = preds,real=real
    )
  }
}
save_to_bucket(prediction_analysis_results,
               file="tar_vs_untar_prediction_analysis_results.RData",
               bucket = "gs://bic_data_analysis/pass1a/metabolomics/")

We now take the predicted and real values and estimate the prediction accuracy in different ways.

prediction_analysis_results =  load_from_bucket(
  "tar_vs_untar_prediction_analysis_results.RData",
    "gs://bic_data_analysis/pass1a/metabolomics/",F)
prediction_analysis_results = prediction_analysis_results[[1]]

results_metrics = list()
for(nn in names(prediction_analysis_results)){
  preds = prediction_analysis_results[[nn]]$preds
  real = prediction_analysis_results[[nn]]$real
  tar_name = strsplit(nn,split=";")[[1]][1]
  untar_name = strsplit(nn,split=";")[[1]][2]
  y = metabolomics_processed_datasets[[tar_name]]$normalized_data[[1]]
  colnames(preds) = rownames(y)
  colnames(real) = rownames(y)
  tar_name = simplify_metab_dataset_name(tar_name)
  untar_name = simplify_metab_dataset_name(untar_name)
  currtissue = strsplit(tar_name,split=",")[[1]][1]
  tar_name = gsub(paste(currtissue,",",sep=""),"",tar_name)
  untar_name = gsub(paste(currtissue,",",sep=""),"",untar_name)
  if(! currtissue %in% names(results_metrics)){
    results_metrics[[currtissue]] = list()
  }
  if(! tar_name %in% names(results_metrics[[currtissue]])){
    results_metrics[[currtissue]][[tar_name]] = list()
  }
  
  rhos = format(diag(cor(preds,real,method="spearman")),digits=3)
  rhos = as.numeric(rhos)
  SEs = colSums((preds-real)^2)
  MSEs = SEs / nrow(preds)
  RMSE = sqrt(MSEs)
  rMSE = MSEs / apply(y,1,var)
  CoVs = apply(y,1,sd) / apply(y,1,mean)
  discCoVs = cut(CoVs,breaks = 2,ordered_result = T)
  
  results_metrics[[currtissue]][[tar_name]][[untar_name]] = data.frame(
    rhos,MSEs,RMSE,rMSE,CoVs,discCoVs
  )
}

We now present a few summary plots.

for(tissue in names(results_metrics)){
  for(tar in names(results_metrics[[tissue]])){
    l = results_metrics[[tissue]][[tar]]
    rho_vs_cv = c()
    for(untar in names(l)){
      m = l[[untar]][,c("rhos","discCoVs")] # take the current matrix
      m = cbind(rep(untar,nrow(m)),m)
      m$discCoVs = as.numeric(m$discCoVs)
      rho_vs_cv = rbind(rho_vs_cv,m)
    }
    colnames(rho_vs_cv)[1] = "dataset"
    boxplot(rhos~discCoVs:dataset,data=rho_vs_cv,las=2,
            ylab="Spearman",xlab = "",ylim=c(0,1),
            main = paste(tissue,tar,sep=","))
  }
}

As additional references, we train below additional models. First, we check the prediction of naive models that use technical and clinical covariates only. Second, we use multi-task regression and deep learning models.

cov_prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  nn1_tissue = strsplit(nn1,split=",")[[1]][1]
  nn1_tissue = gsub("_powder","",nn1_tissue)
  if(grepl("untargeted",nn1)){next}
  print(nn1)
  y = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
  y_vials = colnames(y)
  bid_y = merged_dmaqc_data[colnames(y),"bid"]
  colnames(y) = bid_y
  y = t(as.matrix(y))
  if(ncol(y)>1000){next}
  cov_cols = c("animal.registration.sex",
             "acute.test.weight",
             "acute.test.distance",
             "animal.key.timepoint")
  covs = merged_dmaqc_data[y_vials,cov_cols]
  x = covs
  
  # Run the regressions
  folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
  numFeatures = min(ncol(x),2000)
  preds = c();real=c()
  for(i in 1:ncol(y)){
    y_i = y[,1]
    i_preds = c();i_real=c()
    for(j in 1:nfolds){
      print(j)
      tr_x = x[folds!=j,]
      tr_yi = y_i[folds!=j]
      te_x = x[folds==j,]
      te_y = y_i[folds==j]
      # random forest
      model = randomForest(tr_yi,x=tr_x,ntree = 20)
      te_preds = predict(model,newdata = te_x)
      i_preds = c(i_preds,te_preds)
      i_real = c(i_real,te_y)
    }
    preds = cbind(preds,i_preds)
    real = cbind(real,i_real)
  }
  cov_prediction_analysis_results[[nn1]] = list(
      preds = preds,real=real
    )
}

# preds = c();real=c()
# for(j in 1:nfolds){
#   tr_x = x[folds!=j,]
#   tr_y = y[folds!=j,]
#   te_x = x[folds==j,]
#   te_y = y[folds==j,]
#   model = MTL_wrapper(tr_x,tr_y,type="Regression", Regularization="L21")
#   te_preds = predict(model,te_x)
#   real = rbind(real,te_y)
#   preds = rbind(preds,te_preds)
# }
# diag(cor(preds,real))

# Using PLS regression
# library(pls)
# pls_model = plsr(y~x,ncomp = 5,validation="LOO")
# eval = MSEP(pls_model)
# 
# y_pca = prcomp(y)
# plot(y_pca)
# explained_var = y_pca$sdev^2/sum(y_pca$sdev^2)
# y_pca_matrix = y_pca$x[,1:10]
# 
# # regress out sex, weight
# 
# get_explained_variance_using_PCA(x,y)
# x = apply(x,2,regress_out,covs=covs)
# y = apply(y,2,regress_out,covs=covs)
# get_explained_variance_using_PCA(x,y)
LS0tCnRpdGxlOiAiQklDIG1ldGFib2xvbWljcyBkYXRhIGFuYWx5c2lzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwotLS0KCkluIHRoaXMgZG9jdW1lbnQgd2UgcHJlc2VudCB0aGUgam9pbnQgYW5hbHlzaXMgb2YgdGhlIFBBU1MxQSBtZXRhYm9sb21pY3MgZGF0YXNldHMuCgojIExvYWQgYWxsIGRhdGFzZXRzCgpMb2FkIHRoZSBkYXRhIGZyb20gdGhlIGNsb3VkLCBpbmNsdWRpbmc6IHBoZW5vdHlwaWMgZGF0YSwgbWV0YWJvbG9taWMgZGF0YXNldHMsIGFuZCBtZXRhYm9sb21pY3MgZGljdGlvbmFyeS4KCmBgYHtyLHJlc3VsdHM9J2hpZGUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0Kc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9zdXBlcnZpc2VkX25vcm1hbGl6YXRpb25fZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL3Vuc3VwZXJ2aXNlZF9ub3JtYWxpemF0aW9uX2Z1bmN0aW9ucy5SIikKc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9nY3BfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL2Fzc29jaWF0aW9uX2FuYWx5c2lzX21ldGhvZHMuUiIpCnNvdXJjZSgifi9EZXNrdG9wL3JlcG9zL21vdHJwYWMtYmljLW5vcm0tcWMvdG9vbHMvZGF0YV9hdXhfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjL3Rvb2xzL3ByZWRpY3Rpb25fbWxfdG9vbHMuUiIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KSAjIGZvciBjbGFzc2lmaWNhdGlvbiB0ZXN0cwoKIyBMb2FkIHRoZSBkbWFxYyBkYXRhCm1lcmdlZF9kbWFxY19kYXRhID0gIGxvYWRfZnJvbV9idWNrZXQoIm1lcmdlZF9kbWFxY19kYXRhMjAxOS0xMC0xNS5SRGF0YSIsCiAgICAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvcGhlbm9fZG1hcWMvIixGKQptZXJnZWRfZG1hcWNfZGF0YSA9IG1lcmdlZF9kbWFxY19kYXRhW1sxXV0Kcm93bmFtZXMobWVyZ2VkX2RtYXFjX2RhdGEpID0gYXMuY2hhcmFjdGVyKG1lcmdlZF9kbWFxY19kYXRhJHZpYWxfbGFiZWwpCiMgZGVmaW5lIHRoZSB0aXNzdWUgdmFyaWFibGUKbWVyZ2VkX2RtYXFjX2RhdGEkdGlzc3VlID0gbWVyZ2VkX2RtYXFjX2RhdGEkc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgZGVmaW5lIHRoZSB0aW1lIHRvIGZyZWV6ZSB2YXJpYWJsZQptZXJnZWRfZG1hcWNfZGF0YSR0aW1lX3RvX2ZyZWV6ZSA9IG1lcmdlZF9kbWFxY19kYXRhJGNhbGN1bGF0ZWQudmFyaWFibGVzLnRpbWVfZGVhdGhfdG9fY29sbGVjdF9taW4gKyAKICBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy50aW1lX2NvbGxlY3RfdG9fZnJlZXplX21pbgoKIyBjb2wgdGltZSB2cy4gY29udHJvbAojIGRmID0gZGF0YS5mcmFtZSgKIyAgIGJpZCA9IG1lcmdlZF9kbWFxY19kYXRhJGJpZCwKIyAgIGVkdGFfY29sX3RpbWUgPSBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy5lZHRhX2NvbGxfdGltZSwKIyAgIHRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemUsCiMgICBpc19jb250cm9sID0gbWVyZ2VkX2RtYXFjX2RhdGEkYW5pbWFsLmtleS5pc19jb250cm9sLAojICAgdHAgPSBtZXJnZWRfZG1hcWNfZGF0YSRhbmltYWwua2V5LnRpbWVwb2ludCwKIyAgIHRpc3N1ZSA9IG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgKQojIGRmID0gdW5pcXVlKGRmKQojIGJveHBsb3QoZWR0YV9jb2xfdGltZS8zNjAwIH4gaXNfY29udHJvbCxkZikKIyBib3hwbG90KGVkdGFfY29sX3RpbWUvMzYwMCAtIHRwIH4gaXNfY29udHJvbCxkZikKIyB3aWxjb3gudGVzdChlZHRhX2NvbF90aW1lLzM2MDAgfiBpc19jb250cm9sLGRmKQoKIyBibG9vZCBmcmVlemUgdGltZXMKYmxvb2Rfc2FtcGxlcyA9IAogIG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uID09CiAgIkVEVEEgUGxhc21hIgpibG9vZF9mcmVlemVfdGltZSA9IAogIGFzLmRpZmZ0aW1lKG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3NpbmcudF9mcmVlemUsdW5pdHMgPSAibWlucyIpIC0KICBhcy5kaWZmdGltZShtZXJnZWRfZG1hcWNfZGF0YSRzcGVjaW1lbi5wcm9jZXNzaW5nLnRfZWR0YXNwaW4sdW5pdHM9Im1pbnMiKQpibG9vZF9mcmVlemVfdGltZSA9IGFzLm51bWVyaWMoYmxvb2RfZnJlZXplX3RpbWUpCnRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemVbYmxvb2Rfc2FtcGxlc10gPSAKICBibG9vZF9mcmVlemVfdGltZVtibG9vZF9zYW1wbGVzXQoKIyBMb2FkIG91ciBwYXJzZWQgbWV0YWJvbG9taWNzIGRhdGFzZXRzCm1ldGFib2xvbWljc19idWNrZXRfb2JqID0gbG9hZF9mcm9tX2J1Y2tldCgKICBmaWxlID0gIm1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNfcGFzczFhX2V4dGVybmFsMS5SRGF0YSIsCiAgYnVja2V0ID0gImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iKQptZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzID0gbWV0YWJvbG9taWNzX2J1Y2tldF9vYmokbWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cwoKYGBgCkRlZmluZSB0aGUgdmFyaWFibGVzIHRvIGJlIGFkanVzdGVkIGZvcjoKCmBgYHtyfQpiaW9zcGVjX2NvbHMgPSBjKAogICJhY3V0ZS50ZXN0LmRpc3RhbmNlIiwKICAiY2FsY3VsYXRlZC52YXJpYWJsZXMudGltZV90b19mcmVlemUiLAogICMgImNhbGN1bGF0ZWQudmFyaWFibGVzLmVkdGFfY29sbF90aW1lIiwgIyBubyBuZWVkIC0gc2VlIGNvZGUgYWJvdmUgZm9yIGJsb29kCiAgImJpZCIgIyByZXF1aXJlZCBmb3IgbWF0Y2hpbmcgZGF0YXNldHMKICApCmRpZmZlcmVudGlhbF9hbmFseXNpc19jb2xzID0gYygKICAiYW5pbWFsLnJlZ2lzdHJhdGlvbi5zZXgiLAogICJhbmltYWwua2V5LnRpbWVwb2ludCIsCiAgImFuaW1hbC5rZXkuaXNfY29udHJvbCIKKQpwaXBlbGluZV9xY19jb2xzID0gYygic2FtcGxlX29yZGVyIikKYGBgCgojIExvZy10cmFuc2Zvcm06IGVmZmVjdCBvbiB2YXJpYW5jZQoKU29tZSBzaXRlcyBkbyBub3QgdXNlIHRoZSBsb2cgdHJhbnNmb3JtYXRpb24gb24gdGhlaXIgZGF0YXNldC4gSW4gdGhpcyBzZWN0aW9uIHdlIHBsb3QgdGhlIGNvZWZmaWNpZW50IG9mIHZhcmlhdGlvbiBhcyBhIGZ1bmN0aW9uIG9mIHRoZSBtZWFuIGluc3RlbnNpdHkuIFdlIHRha2UgYSBzaW5nbGUgZGF0YXNldCBhcyBhbiBleGFtcGxlIHRvIHNob3cgaG93IGxvZy10cmFuc2Zvcm1lZCBkYXRhIGhhdmUgcmVkdWNlZCBkZXBlbmRlbmN5IGFuZCBzbW9vdGhlciBwbG90cy4KCkFzIGFuIGFkZGl0aW9uYWwgYW5hbHlzaXMgd2UgYWxzbyBwbG90IHRoZSBudW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgcGVyIG1ldGFib2xpdGUgYXMgYSBmdW5jdGlvbiBvZiBpdHMgbWVhbiBpbnRlbnNpdHkuIFdlIHNob3cgdGhhdCB3aGlsZSB0aGVyZSBpcyBoaWdoIGNvcnJlbGF0aW9uIHNvbWUgbWlzc2luZyB2YWx1ZXMgYXBwZWFyIGluIGZhaXJlbHkgaGlnaCBpbnRlbnNpdGllcy4gVGhpcyBpcyBpbXBvcnRhbnQgZm9yIGltcHV0YXRpb24gYXMgc29tZSBzaXRlcyB1c2Ugc29tZSBmaXhlZCBsb3cgdmFsdWUgaW5zdGVhZCBvZiBrbm4gaW1wdXRhdGlvbi4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCiMgUGxvdCBjdiB2cyBtZWFucwpsaWJyYXJ5KGdwbG90cykKZCA9IG1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNbWyJ3aGl0ZV9hZGlwb3NlX3Bvd2RlcixtZXRhYl91X2hpbGljcG9zLHVubmFtZWQiXV0KZHggPSBkJHNhbXBsZV9kYXRhCkNvVjwtZnVuY3Rpb24oeCl7cmV0dXJuKHNkKHgsbmEucm0gPSBUKS9tZWFuKHgsbmEucm09VCkpfQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKQ29WcyA9IGFwcGx5KGR4LDEsQ29WKQppbmRzID0gIWlzLm5hKENvVnMpCmRmID0gZGF0YS5mcmFtZShNZWFuX2ludGVuc2l0eSA9IGRtZWFuc1tpbmRzXSxDb1YgPSBDb1ZzW2luZHNdKQpwbG90KENvVn5NZWFuX2ludGVuc2l0eSxkZixjZXg9MC41LHBjaD0yMCxtYWluPSJSYXcgZGF0YSIpCmxpbmVzKGxvd2VzcyhDb1Z+TWVhbl9pbnRlbnNpdHksZGYpLGx0eT0yLGx3ZD0yLGNvbD0iYmx1ZSIpCgojIFJlcGVhdCBhZnRlciBsb2cyCmR4ID0gbG9nKDErZCRzYW1wbGVfZGF0YSxiYXNlPTIpCmRtZWFucyA9IGFwcGx5KGR4LDEsbWVhbixuYS5ybT1UKQpDb1ZzID0gYXBwbHkoZHgsMSxDb1YpCmluZHMgPSAhaXMubmEoQ29WcykKZGYgPSBkYXRhLmZyYW1lKE1lYW5faW50ZW5zaXR5ID0gZG1lYW5zW2luZHNdLENvViA9IENvVnNbaW5kc10pCnBsb3QoQ29Wfk1lYW5faW50ZW5zaXR5LGRmLGNleD0wLjUscGNoPTIwLG1haW49IkxvZzIgZGF0YSIpCmxpbmVzKGxvd2VzcyhDb1Z+TWVhbl9pbnRlbnNpdHksZGYpLGx0eT0yLGx3ZD0yLGNvbD0iYmx1ZSIpCgojIFBsb3QgbnVtYmVyIG9mIE5BcyB2cyBpbnRlbnNpdHkgbWVhbgpkeCA9IGxvZygxK2Qkc2FtcGxlX2RhdGEsYmFzZT0yKQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKbnVtX25hcyA9IHJvd1N1bXMoaXMubmEoZHgpKQpkZiA9IGRhdGEuZnJhbWUoTnVtX05BcyA9IG51bV9uYXNbaW5kc10sTWVhbl9pbnRlbnNpdHkgPSBkbWVhbnNbaW5kc10pCnJobyA9IGNvcihkZiROdW1fTkFzLGRmJE1lYW5faW50ZW5zaXR5LG1ldGhvZD0ic3BlYXJtYW4iKQpyaG8gPSBmb3JtYXQocmhvLGRpZ2l0cz0yKQpwbG90KE51bV9OQXN+TWVhbl9pbnRlbnNpdHksZGYsY2V4PTAuNSxwY2g9MjAsCiAgICAgbWFpbj1wYXN0ZSgiU3BlYXJtYW46IixyaG8pKQoKCmBgYAoKIyBMb2FkIHRoZSBwcmVwcm9jZXNzZWQgYW5kIG5vcm1hbGl6ZWQgZGF0YQoKYGBge3J9Cm1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMgPSBsb2FkX2Zyb21fYnVja2V0KAogIGZpbGU9Im1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMxMTE4MjAxOS5SRGF0YSIsCiAgYnVja2V0ID0gImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iCilbWzFdXQoKIyBSZWR1Y2UgdGhlIG1ldGFkYXRhIHRvIHRoZSBzZWxlY3RlZCBjb2x1bW5zCmZvcihjdXJybmFtZSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgY3Vycl9kYXRhID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbY3Vycm5hbWVdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogICMgb3JnYW5pemUgdGhlIG1ldGFkYXRhCiAgY3Vycl9tZXRhID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoY3Vycl9kYXRhKSwKICAgICAgICB1bmlvbihiaW9zcGVjX2NvbHMsZGlmZmVyZW50aWFsX2FuYWx5c2lzX2NvbHMpXQogICMgcmVtb3ZlIG1ldGFkYXRhIHZhcmlhYmxlcyB3aXRoIHRvbyBtYW55IE5BcwogIG5hX2NvdW50cyA9IGFwcGx5KGlzLm5hKGN1cnJfbWV0YSksMixzdW0pCiAgY3Vycl9tZXRhID0gY3Vycl9tZXRhWyxuYV9jb3VudHMvbnJvdyhjdXJyX21ldGEpIDwgMC4xXQogIG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW2N1cnJuYW1lXV0kc2FtcGxlX21ldGFfcGFyc2VkID0gY3Vycl9tZXRhCn0KYGBgCgojIExvZy10cmFuc2Zvcm06IGVmZmVjdCBvbiBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgaW4gdGFyZ2V0ZWQgZGF0YQoKVW50YXJnZXRlZCBkYXRhIGFyZSB0eXBpY2FsbHkgbG9nLXRyYW5zZm9ybWVkIGFuZCBhbmFseXplZCB1c2luZyBsaW5lYXIgbW9kZWxzLiBPbiB0aGUgb3RoZXIgaGFuZCwgY29uY2VudHJhdGlvbiBkYXRhIGFyZSBzb21ldGltZXMgYW5hbHl6ZWQgd2l0aCB0aGUgc2FtZSB0eXBlIG9mIG1vZGVscyBidXQgdXNpbmcgdGhlIG9yaWdpbmFsIGRhdGEuIFRoaXMgcmFpc2VzIGEgcHJvYmxlbSBpZiB3ZSB3aXNoIHRvIGNvbXBhcmUgZXhhY3Qgc3RhdGlzdGljcyBmcm9tIHRoZXNlIGRhdGEuIEluIHRoaXMgc2VjdGlvbiB3ZSBwZXJmb3JtIHJlc2lkdWFsIGFuYWx5c2lzIGZvciBzaW5nbGUgbWV0YWJvbGl0ZXMuIE91ciBnb2FsIGlzIHRvIGlkZW50aWZ5IGlmIGNvbmNlbnRyYXRpb24gZGF0YSBiZWhhdmVzICJub3JtYWxseSIgd2hlbiBub3QgbG9nLXRyYW5zZm9ybWVkLiBUaGUgYW5hbHlzaXMgYmVsb3cgZXhhbWluZXMgdGhlIHJlc2lkdWFscyBvZiB0aGUgZGF0YSBhZnRlciBmaXR0aW5nIGxpbmVhciBtb2RlbHMgZm9yIGVhY2ggbWV0YWJvbGl0ZSwgYWRqdXN0aW5nIGZvciBmcmVlemUgdGltZSBhbmQgc2V4LiBXZSB0aGVuIGNvbXBhcmUgdGhlIHJlc3VsdHMgd2l0aCBhbmQgd2l0aG91dCB0aGUgbG9nLXRyYW5zZm9ybWF0aW9uLCBjb3VudGluZyB0aGUgbnVtYmVyIG9mIG1ldGFib2xpdGVzIHdpdGggYSBzaWduaWZpY2FudCBldmlkZW5jZSBmb3Igbm9uLW5vcm1hbGx5IGRpc3RyaWJ1dGVkIHJlc2lkdWFscy4gCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiMgY2hlY2sgZm9yIG5vcm1hbGl0eSB1c2luZyB0aGUgS29sbW9nb3Jvdi1TbWlybm92IHRlc3QKaXNfbm9ybWFsX3Rlc3Q8LWZ1bmN0aW9uKHYpewogIGlmKHNkKHYsbmEucm0gPSBUKT09MCl7cmV0dXJuKDApfQogIHRyeSh7cmV0dXJuKHNoYXBpcm8udGVzdCh2KSRwLnZhbHVlKX0pCiAgIyBUaGUgU2hhcGlybyB0ZXN0IG1heSBmYWlsIGlmIHRoZSBzZCBvZiB2IGlzIHplcm8KICByZXR1cm4oa3MudGVzdCh2LCJwbm9ybSIsbWVhbih2LG5hLnJtPVQpLHNkKHYsbmEucm0gPSBUKSkkcC52YWx1ZSkKfQojIGdvIG92ZXIgdGhlIG5hbWVkIGRhdGFzZXRzLCBnZXQgYSBsb2dnZWQgYW5kIGFuIHVubG9nZ2VkIHZlcnNpb24gb2YKIyB0aGUgZGF0YSwgdXNlIHRoZXNlIGFzIGlucHV0cyBmb3IgdGhlIHJlZ3Jlc3Npb24KcmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cyA9IGxpc3QoKQpmb3Iobm4xIGluIG5hbWVzKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMpKXsKICBpZighbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kaXNfdGFyZ2V0ZWQpe25leHR9CiAgeF9sb2cgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogIHhfdW5sb2cgPSAyXnhfbG9nCiAgCiAgIyB0YWtlIHRoZSBjb3ZhcmlhdGVzLCBpZ25vcmUgZGlzdGFuY2VzCiAgeF9tZXRhID0gdW5pcXVlKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHNhbXBsZV9tZXRhX3BhcnNlZCkKICBjdXJyX2NvdnMgPSB4X21ldGFbLGludGVyc2VjdChjb2xuYW1lcyh4X21ldGEpLGJpb3NwZWNfY29sc1syXSldCiAgY3Vycl9jb3ZzID0gZGF0YS5mcmFtZShjdXJyX2NvdnMsCiAgICAgICAgICAgc2V4PXhfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCkKICAKICAjIGdldCB0aGUgbG0gb2JqZWN0cwogIGN1cnJfbW9kZWxzID0gbGlzdCgpCiAgZm9yKHRwIGluIHVuaXF1ZSh4X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQpKXsKICAgICAgcmVzX2xvZyA9IGFwcGx5KAogICAgICAgIHhfbG9nLDEsCiAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgIHRwcyA9IHhfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCx0cD10cCwKICAgICAgICBpc19jb250cm9sID0geF9tZXRhJGFuaW1hbC5rZXkuaXNfY29udHJvbCwKICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1UCiAgICAgICkKICAgICAgcmVzX3VubG9nID0gYXBwbHkoCiAgICAgICAgeF91bmxvZywxLAogICAgICAgIHBhc3MxYV9zaW1wbGVfZGlmZmVyZW50aWFsX2FidW5kYW5jZSwKICAgICAgICB0cHMgPSB4X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQsdHA9dHAsCiAgICAgICAgaXNfY29udHJvbCA9IHhfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgY292cyA9IGN1cnJfY292cyxyZXR1cm5fbW9kZWw9VAogICAgICApCiAgICAgIGlzX25vcm0gPSBjYmluZCgKICAgICAgICBzYXBwbHkocmVzX2xvZyxmdW5jdGlvbih4KWlzX25vcm1hbF90ZXN0KHJlc2lkdWFscyh4KSkpLAogICAgICAgIHNhcHBseShyZXNfdW5sb2csZnVuY3Rpb24oeClpc19ub3JtYWxfdGVzdChyZXNpZHVhbHMoeCkpKQogICAgICApCiAgICAgIGNvbG5hbWVzKGlzX25vcm0pID0gYygibG9nIiwibm90IGxvZyIpCiAgICAgIGN1cnJfbW9kZWxzW1thcy5jaGFyYWN0ZXIodHApXV0gPSBpc19ub3JtCiAgICAgIAogICAgICAjICMgdGVzdCBhIHNwZWNpZmljIG1vZGVsCiAgICAgICMgaW5kID0gMjQ2CiAgICAgICMgcGxvdCh4X3VubG9nW2luZCxdLHhfbG9nW2luZCxdKQogICAgICAjIAogICAgICAjIHJlc2lkc19sb2cgPSByc3RhbmRhcmQocmVzX2xvZ1tbaW5kXV0pCiAgICAgICMgZml0X2xvZyA9IHJlc19sb2dbW2luZF1dJGZpdHRlZC52YWx1ZXMKICAgICAgIyByZXNpZHNfdW5sb2cgPSByc3RhbmRhcmQocmVzX3VubG9nW1tpbmRdXSkKICAgICAgIyBmaXRfdW5sb2cgPSByZXNfdW5sb2dbW2luZF1dJGZpdHRlZC52YWx1ZXMKICAgICAgIyAKICAgICAgIyBwbG90KGZpdF9sb2cscmVzaWRzX2xvZykKICAgICAgIyBwbG90KGZpdF91bmxvZyxyZXNpZHNfdW5sb2cpCiAgfQogIHJlc2lkdWFsX2FuYWx5c2lzX3Jlc3VsdHNbW25uMV1dID0gY3Vycl9tb2RlbHMKfQoKIyBJcyB0aGVyZSBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIG9wdGlvbnM/CmxvZ192c191bmxvZ19zdW1tX21hdCA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KQogICAgICAgICAgd2lsY294LnRlc3QoeVssMV0seVssMl0scGFpcmVkID0gVCxhbHRlcm5hdGl2ZSA9ICJnIikkcC52YWx1ZSkpCgojIENvdW50IHRoZSBudW1iZXIgb2Ygbm9uLW5vcm1hbCBtZXRhYm9saXRlcwpudW1fbm9ubm9ybWFsX2xvZyA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KXN1bSh5WywxXTwwLjA1KSkpCm51bV9ub25ub3JtYWxfbG9nID0gCiAgbnVtX25vbm5vcm1hbF9sb2dbLG9yZGVyKGNvbG5hbWVzKG51bV9ub25ub3JtYWxfbG9nKSldCm51bV9ub25ub3JtYWxfdW5sb2cgPSBzYXBwbHkocmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cywKICAgIGZ1bmN0aW9uKHgpc2FwcGx5KHgsCiAgICAgICAgZnVuY3Rpb24oeSlzdW0oeVssMl08MC4wNSkpKQpudW1fbm9ubm9ybWFsX3VubG9nID0gCiAgbnVtX25vbm5vcm1hbF91bmxvZ1ssb3JkZXIoY29sbmFtZXMobnVtX25vbm5vcm1hbF91bmxvZykpXQoKbGlicmFyeShjb3JycGxvdCkKcGFyKG1hciA9IGMoNSw1LDUsMTApKQpub3JtZGlmZnMgPSB0KG51bV9ub25ub3JtYWxfbG9nKS0gdChudW1fbm9ubm9ybWFsX3VubG9nKQpjb3JycGxvdChub3JtZGlmZnMsaXMuY29yciA9IEYsdGwuY2V4ID0gMC43KQpgYGAKCgojIFRyYWdldGVkIHZzLiBVbnRhcmdldGVkOiBzaW5nbGUgbWV0YWJvbGl0ZSBjb21wYXJpc29uCgojIyBDb21wdXRlIHN0YXRpc3RpY3MgZm9yIGVhY2ggZGF0YXNldAoKQ29tcGFyZSBvdmVybGFwcywgZWZmZWN0IHNpemVzLCBhbmQgY29ycmVsYXRpb25zIHdpdGhpbiB0aXNzdWVzLiBDb21wYXJlIHRhcmdldGVkLXVudGFyZ2V0ZWQgcGFpcnMgb25seS4gRm9yIGRpZmZlcmVudGlhbCBhbmFseXNpcyB3ZSB1c2UgdGhlIHNhbWUgbW9kZWwgYXMgaW4gdGhlIGFuYWx5c2lzIGFib3ZlLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQoKIyBUcmFuc2Zvcm0gdGhlIGRhdGEgbWF0cml4IHRvIGhhdmUgY29tcG91bmQgbmFtZXMgYXMgcm93IG5hbWVzLgojIFRoaXMgcmVxdWlyZXMgcmVtb3Zpbmcgcm93cyB3aXRob3V0IG5hbWVzIGFuZCBjaGFuZ2luZyB0aGUgcm93bmFtZXMgb2YKIyB0aGUgaW5wdXQgbWF0cml4IHguCiMgQWxzbywgZG8gbm90IGFzc3VtZSB0aGF0IHRoZSByb3cgYW5ub3RhdGlvbiBvcmRlIGZpdHMgdGhlIGRhdGEgbmVjZXNzYXJpbHkKZXh0cmFjdF9ieV9jb21wX25hbWVfZnJvbV9yb3dfYW5ub3Q8LWZ1bmN0aW9uKHgscm93X2Fubm90X3gpewogICMgZ2V0IHRoZSBjb2x1bW4gdGhhdCBoYXMgdGhlIHJvdyBuYW1lcyBvZiB4IG9yIGF0IGxlYXN0CiAgIyBoYXZlIHRoZSBncmVhdGVzdCBpbnRlcnNlY3Rpb24gd2l0aCBpdAogIGludF9zaXplcyA9IGFwcGx5KHJvd19hbm5vdF94LDIsZnVuY3Rpb24oeCx5KWxlbmd0aChpbnRlcnNlY3QoeCx5KSkseT1yb3duYW1lcyh4KSkKICBpbmQgPSB3aGljaChpbnRfc2l6ZXM9PW1heChpbnRfc2l6ZXMsbmEucm0gPSBUKSlbMV0KICAjIHVwZGF0ZSB0aGUgYW5ub3RhdGlvbiB0YWJsZSB0byBoYXZlIG9ubHkgcm93cyB0aGF0IGhhdmUgCiAgIyBhIHJvdyBpbiB4IGFuZCB0aGVuIHVwZGF0ZSB0aGUgcm93bmFtZXMgdG8gYmUgZnJvbSB0aGUgCiAgIyBzZWxlY3RlZCBjb2x1bW4KICByb3dfYW5ub3RfeCA9IHJvd19hbm5vdF94W2lzLmVsZW1lbnQocm93X2Fubm90X3hbLGluZF0sc2V0PXJvd25hbWVzKHgpKSxdCiAgcm93bmFtZXMocm93X2Fubm90X3gpID0gcm93X2Fubm90X3hbLGluZF0KICAjIHdlIGNhbiBub3cgaW50ZXJzZWN0IHggYW5kIHRoZSBhbm5vdGF0aW9uIHVzaW5nIHRoZWlyIHJvdyBuYW1lcwogICMgYW5kIHVwZGF0ZSB4IGFjY29yZGluZ2x5CiAgc2hhcmVkID0gaW50ZXJzZWN0KHJvd25hbWVzKHJvd19hbm5vdF94KSxyb3duYW1lcyh4KSkKICB4ID0geFtzaGFyZWQsXQogIHJvd19hbm5vdF94ID0gcm93X2Fubm90X3hbc2hhcmVkLF0KICByb3duYW1lcyh4KSA9IHJvd19hbm5vdF94JG1vdHJwYWNfY29tcF9uYW1lCiAgcmV0dXJuKHgpCn0KCnRhcl92c191bnRhcl9ub3JtX3BhaXJzID0gbGlzdCgKICBjKCJsb2cyLGltcCIsImxvZzIsaW1wLFRNTSIpLAogIGMoImxvZzIsaW1wIiwibG9nMixpbXAiKSwKICBjKCJsb2cyLGltcCIsIm1lZCxsb2csaW1wIiksCiAgYygiaW1wLG5vbmUiLCJpbXAsbm9uZSIpLAogIGMoImltcCxub25lIiwiaW1wLFRNTSIpLAogIGMoImltcCxub25lIiwibWVkLGxvZyxpbXAiKQopCgojIFRoZSBsb29wIGJlbG93IGlzIHRoZSBjb3JlIG9mIHRoZSBjb21wdXRhdGlvbnMgZm9yIGNvbXBhcmluZyAKIyB0YXJnZXRlZCBhbmQgdW50YXJnZXRlZCBkYXRhIHdoZW4gdXNpbmcga25vd24gY29tcG91bmQgbmFtZXMKIwojIEZvciBlYWNoIG5vcm1hbGl6YXRpb24gb3B0aW9uIChhIHBhaXIgZnJvbSB0aGUgbGlzdCBhYm92ZSkgd2UgY29tcHV0ZQojIGFsbCBwYWlyd2lzZSBjb3JyZWxhdGlvbiBtYXRyaWNlcyBvZiB0aGUgb3ZlcmxhcHBpbmcgbWV0YWJvbGl0ZXMgYW5kCiMgdGhlIGRpZmZlcmVudGlhbCBhbmFseXNpcyByZXN1bHRzIChhZ2FpbiBvZiB0aGUgb3ZlcmxhcCkuCiMgVGhlc2Ugb2JqZWN0cyBhcmUgdGhlbiB1c2VkIGxhdGVyIGZvciBvdGhlciBxdWFudGl0YXRpdmUgY29tcGFyaXNvbnMKbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzID0gbGlzdCgpCmZvcihub3JtX3BhaXJzIGluIHRhcl92c191bnRhcl9ub3JtX3BhaXJzKXsKICB0YXJfbm9ybV9tZXRob2QgPSBub3JtX3BhaXJzWzFdCiAgdW50YXJfbm9ybV9tZXRob2QgPSBub3JtX3BhaXJzWzJdCiAgc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPSBsaXN0KCkKICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IGMoKQogIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbGlzdCgpCiAgZm9yKG5uMSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgICBubjFfdGlzc3VlID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kdGlzc3VlCiAgICBpZighbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kaXNfdGFyZ2V0ZWQpe25leHR9CiAgICBzaW5nbGVfbWV0YWJvbGl0ZV9jb3Jyc1tbbm4xXV0gPSBsaXN0KCkKICAgIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzW1tubjFdXSA9IE5VTEwKICAgIGZvcihubjIgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogICAgICBpZihubjIgPT0gbm4xKXtuZXh0fQogICAgICBpZihtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRpc190YXJnZXRlZCl7bmV4dH0KICAgICAgbm4yX3Rpc3N1ZSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJHRpc3N1ZQogICAgICBubjJfZGF0YXNldCA9IHBhc3RlKHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMzo0XSxjb2xsYXBzZSA9ICIsIikKICAgICAgaWYobm4xX3Rpc3N1ZSE9bm4yX3Rpc3N1ZSl7bmV4dH0KICAgICAgIyBnZXQgdGhlIG51bWVyaWMgZGF0YXNldHMgYW5kIHRoZWlyIGFubm90YXRpb24KICAgICAgeCA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJG5vcm1hbGl6ZWRfZGF0YVtbdGFyX25vcm1fbWV0aG9kXV0KICAgICAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJG5vcm1hbGl6ZWRfZGF0YVtbdW50YXJfbm9ybV9tZXRob2RdXQogICAgICByb3dfYW5ub3RfeCA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHJvd19hbm5vdAogICAgICByb3dfYW5ub3RfeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJHJvd19hbm5vdAogICAgICAjIHRyYW5zZm9ybSBtZXRhYm9saXRlIG5hbWVzIHRvIHRoZSBtb3RycGFjIGNvbXAgbmFtZQogICAgICB4ID0gZXh0cmFjdF9ieV9jb21wX25hbWVfZnJvbV9yb3dfYW5ub3QoeCxyb3dfYW5ub3RfeCkKICAgICAgeSA9IGV4dHJhY3RfYnlfY29tcF9uYW1lX2Zyb21fcm93X2Fubm90KHkscm93X2Fubm90X3kpCiAgICAgICMgYWxpZ24gdGhlIHNhbXBsZSBzZXRzCiAgICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICAgIGJpZF94ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeCksImJpZCJdICAgIAogICAgICAjIHN0ZXAgMTogbWVyZ2Ugc2FtcGxlcyBmcm9tIHRoZSBzYW1lIEJJRAogICAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICAgIHggPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh4LGJpZF94KQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAgY29sbmFtZXMoeCkgPSBiaWRfeAogICAgICB9CiAgICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgICAgeSA9IGFnZ3JlZ2F0ZV9yZXBlYXRlZF9zYW1wbGVzKHksYmlkX3kpCiAgICAgIH1lbHNlewogICAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgICAgfQogICAgICAjIHN0ZXAgMjogdXNlIHRoZSBzaGFyZWQgYmlvIGlkcwogICAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgICB4ID0gYXMubWF0cml4KHhbLHNoYXJlZF9iaWRzXSkKICAgICAgeSA9IGFzLm1hdHJpeCh5WyxzaGFyZWRfYmlkc10pCiAgICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICAgIHlfbWV0YSA9IAogICAgICAgIHVuaXF1ZShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRzYW1wbGVfbWV0YV9wYXJzZWQpCiAgICAgIHJvd25hbWVzKHlfbWV0YSkgPSB5X21ldGEkYmlkCiAgICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAgIAogICAgICAjIElmIG9uZSBkYXRhc2V0IGlzIGxvZy10cmFuc2Zvcm1lZCBhbmQgdGhlIG90aGVyIGlzIG5vdAogICAgICAjIHRyYW5zZm9ybSBiYWNrIHRvIHRoZSBvcmlnaW5hbCB2YWx1ZXMKICAgICAgaXNfdGFyX2xvZyA9IGdyZXBsKCJsb2ciLHRhcl9ub3JtX21ldGhvZCkKICAgICAgaXNfdW50YXJfbG9nID0gZ3JlcGwoImxvZyIsdW50YXJfbm9ybV9tZXRob2QpCiAgICAgIGlmKGlzX3Rhcl9sb2cgJiYgIWlzX3VudGFyX2xvZyl7CiAgICAgICAgeCA9IDJeeAogICAgICB9CiAgICAgIGlmKCFpc190YXJfbG9nICYmIGlzX3VudGFyX2xvZyl7CiAgICAgICAgeSA9IDJeeQogICAgICB9CiAgICAKICAgICAgIyBJZiBkYXRhIGFyZSBub3QgbG9nLXRyYW5zZm9ybWVkIHRoZW4gc2NhbGUgdGhlIHJvd3MKICAgICAgaWYoIWlzX3Rhcl9sb2cgfHwgIWlzX3VudGFyX2xvZyl7CiAgICAgICAgeCA9IHQoc2NhbGUodCh4KSxjZW50ZXIgPSBULHNjYWxlID0gVCkpCiAgICAgICAgeSA9IHQoc2NhbGUodCh5KSxjZW50ZXIgPSBULHNjYWxlID0gVCkpCiAgICAgIH0KICAgIAogICAgICAjIGdldCB0aGUgc2hhcmVkIG1hdGVib2xpdGVzCiAgICAgIHNoYXJlZF9tZXRhYm9saXRlcyA9IGludGVyc2VjdChyb3duYW1lcyh4KSxyb3duYW1lcyh5KSkKICAgICAgc2hhcmVkX21ldGFib2xpdGVzID0gbmEub21pdChzaGFyZWRfbWV0YWJvbGl0ZXMpCiAgICAgIGlmKGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpPT0wKXtuZXh0fQogICAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0gPSB1bmlvbigKICAgICAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0sCiAgICAgICAgc2hhcmVkX21ldGFib2xpdGVzCiAgICAgICkKICAgIAogICAgICAjIENvbXB1dGUgdGhlIGNvcnJlbGF0aW9uIG1hdHJpY2VzIG9mIHRoZSBzaGFyZWQgbWV0YWJvbGl0ZXMKICAgICAgaWYobGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcyk+MSl7CiAgICAgICAgICBjb3JycyA9Y29yKHQoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSksCiAgICAgICAgICAgICAgICB0KHlbc2hhcmVkX21ldGFib2xpdGVzLF0pLG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgICAgIH0KICAgICAgZWxzZXsKICAgICAgICAgIGNvcnJzID0gY29yKHhbc2hhcmVkX21ldGFib2xpdGVzLF0sCiAgICAgICAgICAgICAgICB5W3NoYXJlZF9tZXRhYm9saXRlcyxdLG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgICAgIH0KICAgIAogICAgICAjIHRha2UgdGhlIGNvdmFyaWF0ZXMgKGlnbm9yZSBkaXN0YW5jZXMpCiAgICAgIGN1cnJfY292X2NvbHMgPSBpbnRlcnNlY3QoY29sbmFtZXMoeV9tZXRhKSxiaW9zcGVjX2NvbHNbMl0pCiAgICAgIGN1cnJfY292cyA9IGRhdGEuZnJhbWUoeV9tZXRhWyxjdXJyX2Nvdl9jb2xzXSkKICAgICAgbmFtZXMoY3Vycl9jb3ZzKSA9IGN1cnJfY292X2NvbHMKICAgICAgY3Vycl9jb3ZzJHNleCA9IHlfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCAjIGFkZCBzZXgKICAgIAogICAgICAjIGRpZmZlcmVudGlhbCBhbmFseXNpcwogICAgICBmb3IodHAgaW4gdW5pcXVlKHlfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCkpewogICAgICAgIGN1cnJfY29udHJvbF90cCA9IE5VTEwKICAgICAgICAjIGlmKHRwID09IDcgfHwgdHAgPT0gNCl7Y3Vycl9jb250cm9sX3RwPTd9CiAgICAgICAgcmVzeCA9IHQoYXBwbHkoCiAgICAgICAgICBtYXRyaXgoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgICAgdHBzID0geV9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1GLAogICAgICAgICAgY29udHJvbF90cCA9IGN1cnJfY29udHJvbF90cAogICAgICAgICkpCiAgICAgICAgcmVzeSA9IHQoYXBwbHkoCiAgICAgICAgICBtYXRyaXgoeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgICAgdHBzID0geV9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1GLAogICAgICAgICAgY29udHJvbF90cCA9IGN1cnJfY29udHJvbF90cAogICAgICAgICkpCiAgICAgICAgIyBBZGQgZGF0YXNldCBpbmZvcm1hdGlvbiwgdGltZSBwb2ludCwgdGlzc3VlCiAgICAgICAgIyBUaGVzZSBhcmUgaW1wb3J0YW50IGFubm90YXRpb25zIGZvciBvdXIgc3VtbWFyeSBtYXRyaXgKICAgICAgICAjIGNhbGxlZCBzaW5nbGVfbWV0YWJvbGl0ZV9kZSBiZWxvdwogICAgICAgIGFkZGVkX2NvbHVtbnMgPSBtYXRyaXgoY2JpbmQoCiAgICAgICAgICByZXAobm4xLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICAgIHJlcChubjIsbGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcykpLAogICAgICAgICAgc2hhcmVkX21ldGFib2xpdGVzLAogICAgICAgICAgcmVwKHRwLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICAgIHJlcChubjFfdGlzc3VlLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKQogICAgICAgICksbnJvdz1sZW5ndGgoc2hhcmVkX21ldGFib2xpdGVzKSkKICAgICAgICByZXN4ID0gY2JpbmQocmVzeCxyZXAoVCxucm93KHJlc3gpKSkKICAgICAgICBjb2xuYW1lcyhyZXN4KVtuY29sKHJlc3gpXSA9ICJpc190YXJnZXRlZCIKICAgICAgICByZXN5ID0gY2JpbmQocmVzeSxyZXAoRixucm93KHJlc3kpKSkKICAgICAgICBjb2xuYW1lcyhyZXN5KVtuY29sKHJlc3kpXSA9ICJpc190YXJnZXRlZCIKICAgICAgICBpZihucm93KHJlc3gpPjEpewogICAgICAgICAgcmVzeCA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0yXSxyZXN4KQogICAgICAgICAgcmVzeSA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0xXSxyZXN5KQogICAgICAgIH0KICAgICAgICBlbHNlewogICAgICAgICAgcmVzeCA9IGMoYWRkZWRfY29sdW1uc1ssLTJdLHJlc3gpCiAgICAgICAgICByZXN5ID0gYyhhZGRlZF9jb2x1bW5zWywtMV0scmVzeSkKICAgICAgIH0KICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHJiaW5kKHNpbmdsZV9tZXRhYm9saXRlX2RlLHJlc3gpCiAgICAgICAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSByYmluZChzaW5nbGVfbWV0YWJvbGl0ZV9kZSxyZXN5KQogICAgICB9CiAgICAgIHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzW1tubjFdXVtbbm4yXV0gPSBjb3JycwogICAgfQogIH0KCiAgIyBSZWZvcm1hdCB0aGUgZGlmZmVyZW50aWFsIGFuYWx5c2lzIHJlc3VsdHMgZm9yIGVhc2llciBjb21wYXJpc29uIGxhdGVyCiAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSBkYXRhLmZyYW1lKHNpbmdsZV9tZXRhYm9saXRlX2RlKQogIG5hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKSA9IGMoImRhdGFzZXQiLCJtZXRhYm9saXRlIiwidHAiLCJ0aXNzdWUiLAogICAgIkVzdCIsIlN0ZCIsIlRzdGF0IiwiUHZhbHVlIiwiaXNfdGFyZ2V0ZWQiKQogIGZvcihjb2wgaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpWy1jKDE6NCldKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSA9IGFzLm51bWVyaWMoCiAgICAgIGFzLmNoYXJhY3RlcihzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbY29sXV0pKQogIH0KICBmb3IoY29sIGluIG5hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKVsxOjRdKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSA9IAogICAgICBhcy5jaGFyYWN0ZXIoc2luZ2xlX21ldGFib2xpdGVfZGVbW2NvbF1dKQogIH0KICAjIFJlbW92ZSBkdXBsaWNhdGlvbnMKICAjIFJvdW5kaW5nIHRoZSBwLXZhbHVlcyAtIG5lY2Vzc2FyeSBmb3IgcmVtb3ZpbmcgZHVwbGljYXRlcwogICMgdXNpbmcgdGhlIHVuaXF1ZSBmdW5jdGlvbgogIHJvd25hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKSA9IE5VTEwKICBmb3Iobm4gaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpKXsKICAgIG5kaWcgPSA1CiAgICBpZihncmVwbCgicHZhbCIsbm4saWdub3JlLmNhc2UgPSBUKSl7CiAgICAgIG5kaWcgPSAxMAogICAgfQogICAgaWYoaXMubnVtZXJpYyhzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSkpewogICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSA9IAogICAgICByb3VuZChzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSxkaWdpdHMgPSBuZGlnKQogICAgfQogIH0KICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSkKICAKICAjIEZpbmFsbHksIHN0b3JlIGFsbCB0aGUgcmVzdWx0cyBmb3IgdGhlIGN1cnJlbnQgbm9ybWFsaXphdGlvbiBwYWlyCiAgcGFpcl9uYW1lID0gcGFzdGUodGFyX25vcm1fbWV0aG9kLHVudGFyX25vcm1fbWV0aG9kLHNlcD0iOyIpCiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSA9IGxpc3QoCiAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHNpbmdsZV9tZXRhYm9saXRlX2RlLAogICAgc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPSBzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycywKICAgIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbmFtZWQyY292ZXJlZF9zaGFyZWRfbWV0YWJvbGl0ZXMKICApCn0KCmBgYAoKV2UgbmV4dCB0cmFuc2Zvcm0gdGhlIGRhdGEgYWJvdmUgaW50byB0YWJsZXMgdGhhdCBjb250YWluIGRhdGEgZm9yIGVhY2ggY29tYmluYXRpb24gb2YgbWV0YWJvbGl0ZSwgdGltZSBwb2ludCwgYW5kIHRpc3N1ZS4gVGhlc2UgYXJlIHRoZW4gdXNlZCBmb3IgZGlmZmVyZW50IG1ldGEtYW5hbHlzZXM6ICgxKSBhIHNpbXBsZSByYW5kb20gZWZmZWN0cyBhbmFseXNpcywgKDIpIHJhbmRvbSBlZmZlY3RzIHdpdGggYSBiaW5hcnkgY292YXJpYXRlIGluZGljYXRpbmcgaWYgYSBkYXRhc2V0IGlzIHRhcmdldGVkIG9yIHVudGFyZ2V0ZWQsICgzKSByZWRvIHRoZSBSRSBtb2RlbCBvZiAoMSkgd2l0aCB0aGUgdGFyZ2V0ZWQgZGF0YSBvbmx5LCBhbmQgKDQpIHJlZG8gdGhlIFJFIG1vZGVsIG9mICgxKSB3aXRoIHRoZSB1bnRhcmdldGVkIGRhdGEgb25seS4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCmxpYnJhcnkobWV0YWZvcikKZm9yKHBhaXJfbmFtZSBpbiBuYW1lcyhub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMpKXsKICBtZXRhX2FuYWx5c2lzX3N0YXRzID0gbGlzdCgpCiAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSAKICAgIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0c1tbcGFpcl9uYW1lXV0kc2luZ2xlX21ldGFib2xpdGVfZGUKICBmb3IodGlzc3VlIGluIHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0aXNzdWUpKXsKICAgIGZvcih0cCBpbiB1bmlxdWUoc2luZ2xlX21ldGFib2xpdGVfZGUkdHApKXsKICAgICAgY3Vycl9zdWJzZXQgPSBzaW5nbGVfbWV0YWJvbGl0ZV9kZVsKICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0aXNzdWU9PXRpc3N1ZSAmCiAgICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0cD09dHAsXQogICAgICBmb3IobWV0YWJvbGl0ZSBpbiB1bmlxdWUoY3Vycl9zdWJzZXQkbWV0YWJvbGl0ZSkpewogICAgICAgIGN1cnJfbWV0X2RhdGEgPSBjdXJyX3N1YnNldFsKICAgICAgICAgIGN1cnJfc3Vic2V0JG1ldGFib2xpdGU9PW1ldGFib2xpdGUsXQogICAgICAgIGN1cnJfbWV0X2RhdGEkdmFyID0gY3Vycl9tZXRfZGF0YSRTdGReMgogICAgICAgIHJlX21vZGVsMSA9IE5VTEw7cmVfbW9kZWwyPU5VTEwKICAgICAgICByZV9tb2RlbF90YXIgPSBOVUxMO3JlX21vZGVsX3VudGFyID0gTlVMTAogICAgICAgIHRyeSh7cmVfbW9kZWwxID0gcm1hLnVuaShjdXJyX21ldF9kYXRhJEVzdCxjdXJyX21ldF9kYXRhJHZhcixtZXRob2Q9IkZFIil9KQogICAgICAgIHRyeSh7cmVfbW9kZWwyID0gcm1hLm12KGN1cnJfbWV0X2RhdGEkRXN0LGN1cnJfbWV0X2RhdGEkdmFyLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RzPWN1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQsbWV0aG9kPSJGRSIpfSkKICAgICAgICB0cnkoe3JlX21vZGVsX3RhciA9IHJtYS51bmkoCiAgICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTEsIkVzdCJdLAogICAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0xLCJ2YXIiXSwKICAgICAgICAgIG1ldGhvZD0iRkUiCiAgICAgICAgKX0pCiAgICAgICAgdHJ5KHtyZV9tb2RlbF91bnRhciA9IHJtYS51bmkoCiAgICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTAsIkVzdCJdLAogICAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0wLCJ2YXIiXSwKICAgICAgICAgIG1ldGhvZD0iRkUiCiAgICAgICAgKX0pCiAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbcGFzdGUobWV0YWJvbGl0ZSx0aXNzdWUsdHAsc2VwPSIsIildXSA9IAogICAgICAgICAgbGlzdChjdXJyX21ldF9kYXRhPWN1cnJfbWV0X2RhdGEscmVfbW9kZWwxPXJlX21vZGVsMSwKICAgICAgICAgICAgcmVfbW9kZWwyID0gcmVfbW9kZWwyLHJlX21vZGVsX3Rhcj1yZV9tb2RlbF90YXIsCiAgICAgICAgICAgIHJlX21vZGVsX3VudGFyID0gcmVfbW9kZWxfdW50YXIpCiAgICAgIH0KICAgIH0KICB9CiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSRtZXRhX2FuYWx5c2lzX3N0YXRzID0KICAgIG1ldGFfYW5hbHlzaXNfc3RhdHMKfQoKYGBgCgojIyBUYXJnZXRlZCB2cy4gVW50YXJnZXRlZDogY29tcG91bmQgb3ZlcmxhcCAKCldlIGZpcnN0IHBsb3QgdGhlIG51bWJlciBhbmQgcGVyY2VudGFnZSBvZiBtZXRhYm9saXRlcyBpbiB0aGUgdGFyZ2V0ZWQgZGF0YXNldHMgdGhhdCBhcmUgbWVhc3VyZWQgaW4gYXQgbGVhc3Qgb25lIHVudGFyZ2V0ZWQgZGF0YXNldC4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpkYXRhc2V0Mm51bV9tZXRhYm9saXRlcyA9IHNhcHBseShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeClucm93KHgkc2FtcGxlX2RhdGEpKQpuYW1lZF9kYXRhc2V0X2NvdmVyYWdlID0gc2FwcGx5KG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzLGxlbmd0aCkKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IGRhdGEuZnJhbWUoCiAgbmFtZSA9IG5hbWVzKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UpLAogIHBlcmNlbnRhZ2UgPSBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlIC8KICBkYXRhc2V0Mm51bV9tZXRhYm9saXRlc1tuYW1lcyhuYW1lZF9kYXRhc2V0X2NvdmVyYWdlKV0sCiAgY291bnQgPSBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlLAogIHRvdGFsID0gZGF0YXNldDJudW1fbWV0YWJvbGl0ZXNbbmFtZXMobmFtZWRfZGF0YXNldF9jb3ZlcmFnZSldCikKIyBhZGQgZGF0YXNldHMgd2l0aCBubyBjb3ZlcmFnZQphbGxfdGFyZ2V0ZWRfZGF0YXNldHMgPSBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKQphbGxfdGFyZ2V0ZWRfZGF0YXNldHMgPSBhbGxfdGFyZ2V0ZWRfZGF0YXNldHNbIWdyZXBsKCJ1bnRhciIsYWxsX3RhcmdldGVkX2RhdGFzZXRzKV0KemVyb19jb3ZlcmFnZV9kYXRhc2V0cyA9IHNldGRpZmYoYWxsX3RhcmdldGVkX2RhdGFzZXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlJG5hbWUpCnplcm9fY292ZXJhZ2VfZGF0YXNldHMgPSBkYXRhLmZyYW1lKAogIG5hbWUgPSB6ZXJvX2NvdmVyYWdlX2RhdGFzZXRzLAogIHBlcmNlbnRhZ2UgPSByZXAoMCxsZW5ndGgoemVyb19jb3ZlcmFnZV9kYXRhc2V0cykpLAogIGNvdW50ID0gcmVwKDAsbGVuZ3RoKHplcm9fY292ZXJhZ2VfZGF0YXNldHMpKSwKICB0b3RhbCA9IGRhdGFzZXQybnVtX21ldGFib2xpdGVzW3plcm9fY292ZXJhZ2VfZGF0YXNldHNdCikKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IHJiaW5kKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHplcm9fY292ZXJhZ2VfZGF0YXNldHMpCm5hbWVkX2RhdGFzZXRfY292ZXJhZ2UgPSAKICBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlW29yZGVyKGFzLmNoYXJhY3RlcihuYW1lZF9kYXRhc2V0X2NvdmVyYWdlJG5hbWUpKSxdCnByaW50KGdncGxvdChuYW1lZF9kYXRhc2V0X2NvdmVyYWdlLCBhZXMoeD1uYW1lLCB5PXBlcmNlbnRhZ2UpKSArIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHdpZHRoPTAuMikgKyBjb29yZF9mbGlwKCkgKwogIGdlb21fdGV4dChkYXRhPW5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsIAogICAgICAgICAgICBhZXMobmFtZSwgcGVyY2VudGFnZSswLjA1LCBsYWJlbD1jb3VudCksIAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuOSksCiAgICAgICAgICAgIHNpemU9NCkgKyAKICBnZ3RpdGxlKCJUYXJnZXRlZCBkYXRhc2V0OiBjb3ZlcmFnZSBieSB1bnRhcmdldGVkIikpCgpgYGAKCiMjIENvbXBhcmlzb24gcmVzdWx0czogbm9ybWFsaXphdGlvbiBtZXRob2RzCgojIyMgU3BlYXJtYW4gY29ycmVsYXRpb25zCgpDb21wYXJlIHRoZSBub3JtYWxpemF0aW9uIG1ldGhvZHMgYnkgdGhlaXIgY29ycmVsYXRpb24gZGlzdHJpYnV0aW9ucy4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCnJlcF9jb3JyZWxhdGlvbnMgPSBjKCkKdGlzc3VlcyA9IHVuaXF1ZShzYXBwbHkobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cyxmdW5jdGlvbih4KXgkdGlzc3VlKSkKZm9yKHBhaXJfbmFtZSBpbiBuYW1lcyhub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMpKXsKICBzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycyA9IAogICAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSRzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycwogIGZvcih0aXNzdWUgaW4gdGlzc3Vlcyl7CiAgICBjdXJyX2RhdGFzZXRzID0gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfY29ycnMpWwogICAgICBncmVwbCh0aXNzdWUsbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfY29ycnMpKQogICAgXQogICAgYmV0d2Vlbl9jb3JycyA9IGMoKQogICAgZm9yKHRhcl9kYXRhc2V0IGluIGN1cnJfZGF0YXNldHMpewogICAgICBsID0gc2luZ2xlX21ldGFib2xpdGVfY29ycnNbW3Rhcl9kYXRhc2V0XV0KICAgICAgYmV0d2Vlbl9jb3JycyA9IGMoYmV0d2Vlbl9jb3Jycyx1bm5hbWUodW5saXN0KHNhcHBseShsLGRpYWcpKSkpCiAgICB9CiAgICByZXBfY29ycmVsYXRpb25zID0gcmJpbmQocmVwX2NvcnJlbGF0aW9ucywKICAgICAgICAgIGMocGFpcl9uYW1lLHRpc3N1ZSxtZWFuKGJldHdlZW5fY29ycnMsbmEucm09VCksc2QoYmV0d2Vlbl9jb3JycyxuYS5ybT1UKSkpCiAgfQp9CnJlcF9jb3JyZWxhdGlvbnMgPSByZXBfY29ycmVsYXRpb25zWyFpcy5uYShyZXBfY29ycmVsYXRpb25zWywzXSksXQpyZXBfY29ycmVsYXRpb25zID0gZGF0YS5mcmFtZSgKICAiTm9ybU1ldGhvZCIgPSByZXBfY29ycmVsYXRpb25zWywxXSwKICAiVGlzc3VlIiA9IHJlcF9jb3JyZWxhdGlvbnNbLDJdLAogICJNZWFuIiA9IGFzLm51bWVyaWMocmVwX2NvcnJlbGF0aW9uc1ssM10pLAogICJTRCIgPSBhcy5udW1lcmljKHJlcF9jb3JyZWxhdGlvbnNbLDRdKQopCnJlcF9jb3JyZWxhdGlvbnMgPSByZXBfY29ycmVsYXRpb25zW29yZGVyKHJlcF9jb3JyZWxhdGlvbnMkVGlzc3VlKSxdCnByaW50KAogICAgZ2dwbG90KHJlcF9jb3JyZWxhdGlvbnMsIGFlcyh4PVRpc3N1ZSwgeT1NZWFuLCBmaWxsPU5vcm1NZXRob2QpKSArCiAgICAgIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCksIHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycpICsKICAgICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1NZWFuLVNELCB5bWF4PU1lYW4rU0QpLG5hLnJtPVQsIAogICAgICAgICAgICAgICAgICAgd2lkdGg9LjIscG9zaXRpb249cG9zaXRpb25fZG9kZ2UoLjkpKQopCgpgYGAKCiMjIyBEaWZmZXJlbnRpYWwgYW5hbHlzaXMgcmVzdWx0cwoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpwdGhyID0gMC4wMDEKZm9yKHRpc3N1ZSBpbiB0aXNzdWVzKXsKICBmb3IocGFpcl9uYW1lIGluIG5hbWVzKG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cykpewogICAgICBtZXRhX2FuYWx5c2lzX3N0YXRzID0gCiAgICAgICAgICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHNbW3BhaXJfbmFtZV1dJG1ldGFfYW5hbHlzaXNfc3RhdHMKICAgICAgbWV0YV9hbmFseXNpc19zdGF0cyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgICAgICAgZ3JlcGwodGlzc3VlLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpKQogICAgICBdCiAgICAgIGlmKGxlbmd0aChtZXRhX2FuYWx5c2lzX3N0YXRzKT09MCl7bmV4dH0KICAgICAgbmFpdmVfYW5hbHlzaXNfdGFyID0gc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsCiAgICAgICAgZnVuY3Rpb24oeClzdW0oeCRjdXJyX21ldF9kYXRhWywiUHZhbHVlIl08cHRociAmCiAgICAgICAgICAgICAgICAgICAgIHgkY3Vycl9tZXRfZGF0YVssImlzX3RhcmdldGVkIl0pKQogICAgICBuYWl2ZV9hbmFseXNpc191bnRhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLAogICAgICAgIGZ1bmN0aW9uKHgpc3VtKHgkY3Vycl9tZXRfZGF0YVssIlB2YWx1ZSJdPHB0aHIgJgogICAgICAgICAgICAgICAgICAgICAheCRjdXJyX21ldF9kYXRhWywiaXNfdGFyZ2V0ZWQiXSkpCiAgICAgIHRiID0gdGFibGUobmFpdmVfYW5hbHlzaXNfdGFyLG5haXZlX2FuYWx5c2lzX3VudGFyKQogICAgICBub25zaWdfaW5fYm90aCA9IHRiWzEsMV0KICAgICAgdGJbMSwxXSA9IDAKICAgICAgYmFycGxvdCh0KHRiKSwKICAgICAgICAgICAgICBsZWdlbmQ9VCx4bGFiPSJOdW1iZXIgb2YgdGFyZ2V0ZWQgZGF0YXNldHMgd2l0aCBwPDAuMDAxIiwKICAgICAgICAgICAgICB5bGFiID0gImxvZzIgbnVtYmVyIG9mIG1ldGFib2xpdGVzIiwKICAgICAgICAgICAgICBtYWluID0gcGFzdGUodGlzc3VlLHBhaXJfbmFtZSkpCiAgfQp9CmBgYAoKCiMjIyBNZXRhLWFuYWx5c2lzIHJlc3VsdHMKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGlicmFyeShWZW5uRGlhZ3JhbSkKcmVxdWlyZShncmlkRXh0cmEpCnB0aHIgPSAwLjAwMQp0aXNzdWVzID0gdW5pcXVlKHNhcHBseShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzLGZ1bmN0aW9uKHgpeCR0aXNzdWUpKQp0YXJfdnNfdW50YXJfY29ycmVsYXRpb25zID0gYygpCmZvcih0aXNzdWUgaW4gdGlzc3Vlcyl7CiAgZm9yKHBhaXJfbmFtZSBpbiBuYW1lcyhub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMpKXsKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHMgPSAKICAgICAgICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHNbW3BhaXJfbmFtZV1dJG1ldGFfYW5hbHlzaXNfc3RhdHMKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHMgID0gbWV0YV9hbmFseXNpc19zdGF0c1sKICAgICAgICBncmVwbCh0aXNzdWUsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cykpCiAgICAgIF0KICAgIGlmKGlzLm51bGwobWV0YV9hbmFseXNpc19zdGF0cykgfHwgbGVuZ3RoKG1ldGFfYW5hbHlzaXNfc3RhdHMpPT0wKXtuZXh0fQogIAogICAgIyBQLXZhbHVlIGZvciB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRhcmdldGVkIGFuZCB1bnRhcmdldGVkCiAgICB0YXJnZXRlZF9kaWZmX3AgPSAKICAgIHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbDIkcHZhbFsyXSkKCiAgICMgUC12YWx1ZXMgLSB0YXJnZXRlZCB2cy4gdW50YXJnZXRlZAogICAgcHZhbHNfdGFyID0gc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsX3RhciRwdmFsKQogICAgcHZhbHNfdW50YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdW50YXIkcHZhbCkKICAgIHB2YWxzX3VudGFyID0gdW5saXN0KHB2YWxzX3VudGFyW3NhcHBseShwdmFsc191bnRhcixsZW5ndGgpPjBdKQogICAgc2lnbmlmaWNhbnRfaW4gPSByZXAoIk5vbmUiLGxlbmd0aChwdmFsc191bnRhcikpCiAgICBzaWduaWZpY2FudF9pbltwdmFsc190YXI8cHRocl0gPSAiVGFyZ2V0ZWQiCiAgICBzaWduaWZpY2FudF9pbltwdmFsc191bnRhcjxwdGhyXSA9ICJVbnRhcmdldGVkIgogICAgc2lnbmlmaWNhbnRfaW5bcHZhbHNfdGFyPHB0aHIgJiBwdmFsc191bnRhcjxwdGhyXSA9ICJCb3RoIgogICAgc2lnbmlmaWNhbnRfZGlmZiA9IHRhcmdldGVkX2RpZmZfcDxwdGhyCiAgICByaG8gPSBjb3IoLWxvZzEwKHB2YWxzX3RhciksLWxvZzEwKHB2YWxzX3VudGFyKSxtZXRob2QgPSAicGVhcnNvbiIpCiAgICAjIEJldGFzIC0gdGFyZ2V0ZWQgdnMuIHVudGFyZ2V0ZWQKICAgIGJldGFzX3RhciA9IAogICAgICBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdGFyJGJldGFbMSwxXSkKICAgIGJldGFzX3VudGFyID0gCiAgICAgIHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF91bnRhciRiZXRhWzEsMV0pCiAgICBiZXRhc191bnRhciA9IHVubGlzdChiZXRhc191bnRhcltzYXBwbHkoYmV0YXNfdW50YXIsbGVuZ3RoKT4wXSkKICAgIGRmID0gZGF0YS5mcmFtZSgKICAgICAgdGFyZ2V0ZWQgPSBiZXRhc190YXIsCiAgICAgIHVudGFyZ2V0ZWQgPSBiZXRhc191bnRhciwKICAgICAgc2lnbmlmaWNhbnRfaW4gPSBzaWduaWZpY2FudF9pbiwKICAgICAgc2lnbmlmaWNhbnRfZGlmZiA9IHNpZ25pZmljYW50X2RpZmYKICAgICkKICAgIHJob19iZXRhID0gY29yKGJldGFzX3VudGFyLGJldGFzX3RhcixtZXRob2QgPSAicGVhcnNvbiIpCiAgICByaG9wID0gY29yLnRlc3QoYmV0YXNfdW50YXIsYmV0YXNfdGFyLG1ldGhvZCA9ICJwZWFyc29uIikkcC52YWx1ZQogICAgcHJpbnQoCiAgICAgIGdncGxvdChkZiwgYWVzKHg9dGFyZ2V0ZWQsIHk9dW50YXJnZXRlZCwKICAgICAgICAgICAgICAgICBzaGFwZT1zaWduaWZpY2FudF9kaWZmLCBjb2xvcj1zaWduaWZpY2FudF9pbikpICsKICAgICAgZ2VvbV9wb2ludCgpICsgZ2VvbV9hYmxpbmUoc2xvcGU9MSxpbnRlcmNlcHQgPSAwKSArIAogICAgICBnZ3RpdGxlKHBhc3RlKHRpc3N1ZSwgcGFpcl9uYW1lLAogICAgICAgICAgICAgICAgICAgICJlZmZlY3RzLCByaG89OiIsZm9ybWF0KHJob19iZXRhLGRpZ2l0cz0yKSkpCiAgICApCiAgCiAgICB0YXJfdnNfdW50YXJfY29ycmVsYXRpb25zID0gcmJpbmQodGFyX3ZzX3VudGFyX2NvcnJlbGF0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKHRpc3N1ZSxwYWlyX25hbWUscmhvLHJob19iZXRhKSkKICAgIyBkcmF3IGEgdmVubiBkaWFncmFtCiAgICBpbnRlcl9hcmVhID0gc3VtKHNpZ25pZmljYW50X2luPT0iQm90aCIpCiAgICB0YXJfYXJlYSA9IHN1bShzaWduaWZpY2FudF9pbj09IlRhcmdldGVkIikgKyBpbnRlcl9hcmVhCiAgICB1bnRhcl9hcmVhID0gc3VtKHNpZ25pZmljYW50X2luPT0iVW50YXJnZXRlZCIpICsgaW50ZXJfYXJlYQogICAgc3VidCA9IHBhc3RlKCJOb3Qgc2lnbmlmaWNhbnQgaW4gYm90aDoiLHRhYmxlKHNpZ25pZmljYW50X2luKVsiTm9uZSJdKQogICAgCiAgICB2ZW5uZyA9IGRyYXcucGFpcndpc2UudmVubih0YXJfYXJlYSx1bnRhcl9hcmVhLGludGVyX2FyZWEsCiAgICAgICAgICAgIGMoIlRhcmdldGVkIiwiVW50YXJnZXRlZCIpLGx0eSA9IHJlcCgiYmxhbmsiLDIpLCAKICAgICAgICAgICAgZmlsbCA9IGMoInBpbmsiLCAiY3lhbiIpLCBhbHBoYSA9IHJlcCgwLjUsIDIpLAogICAgICAgICAgICBjYXQuZGlzdCA9IHJlcCgwLjAxLCAyKSxpbmQ9RikKICAgICMgZ3JpZC5uZXdwYWdlKCkKICAgIGdyaWQuYXJyYW5nZShnVHJlZShjaGlsZHJlbj12ZW5uZyksIAogICAgICAgICAgICAgICAgIHRvcD1wYXN0ZSh0aXNzdWUscGFpcl9uYW1lKSwgYm90dG9tPXN1YnQpCiAgfQp9CgoKYGBgCgojIyBDb21wYXJpc29uIHJlc3VsdHM6IGRldGFpbGVkIGFuYWx5c2lzIG9mIHRoZSBzZWxlY3RlZCBub3JtYWxpemF0aW9uCgojIyMgU3BlYXJtYW4gY29ycmVsYXRpb25zCgpXZSBleGFtaW5lIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHBsYXRmb3JtcyAod2l0aGluIHRpc3N1ZXMpLiBXaGVuZXZlciB0d28gcGxhdGZvcm1zIHNoYXJlIG1vcmUgdGhhbiBhIHNpbmdsZSBtZXRhYm9saXRlIHdlIHBsb3QgYm90aCB0aGUgYXZlcmFnZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBzYW1lIG1ldGFib2xpdGVzIGFuZCBiZXR3ZWVuIG90aGVyIG1ldGFib2xpdGVzLiBBZGRpbmcgdGhlIGF2ZXJhZ2UgY29ycmVsYXRpb24gYmV0d2VlbiBwbGF0Zm9ybXMgYnV0IHdpdGggZGlmZmVyZW50IG1ldGFib2xpdGVzIGlzIGltcG9ydGFudCBhcyBpdCBnaXZlcyBzb21lIHBlcnNwZWN0aXZlIHRvIHdoYXQgYSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbiBpcy4gVGhhdCBpcywgaW4gbWFueSBjYXNlcyBiZWxvdywgdGhlIGF2ZXJhZ2UgY29ycmVsYXRpb24gbWF5IGJlIGdyZWF0ZXIgdGhhbiBleHBlY3RlZC4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KIyBOZXh0IGV4YW1pbmUgdGhlIFNwZWFybWFuIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHBsYXRmb3JtcwptZWFuX2FiczwtZnVuY3Rpb24oeCwuLi4pe3JldHVybihtZWFuKGFicyh4KSwuLi4pKX0Kc2RfYWJzPC1mdW5jdGlvbih4LC4uLil7cmV0dXJuKHNkKGFicyh4KSwuLi4pKX0KZXh0cmFjdF9kaWFnX3ZzX25vbl9kaWFnPC1mdW5jdGlvbihjb3JycyxmdW5jPW1lYW4sLi4uKXsKICBpZihsZW5ndGgoY29ycnMpPT0xKXsKICAgIHJldHVybihjKHNhbWU9ZnVuYyhjb3JycywuLi4pLG90aGVyPU5BKSkKICB9CiAgc2FtZSA9IGZ1bmMoZGlhZyhjb3JycyksLi4uKQogIG90aGVyID0gZnVuYygKICAgIGMoY29ycnNbbG93ZXIudHJpKGNvcnJzLGRpYWcgPSBGKV0pLC4uLikKICByZXR1cm4oYyhzYW1lPXNhbWUsb3RoZXI9b3RoZXIpKQp9CgpzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycyA9CiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzJGBsb2cyLGltcDtsb2cyLGltcGAkc2luZ2xlX21ldGFib2xpdGVfY29ycnMKZm9yKHRhcl9kYXRhc2V0IGluIG5hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzKSl7CiAgbCA9IHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzW1t0YXJfZGF0YXNldF1dCiAgaWYobGVuZ3RoKGwpPT0wKXtuZXh0fQogIGNvcnJfaW5mbyA9IGFzLmRhdGEuZnJhbWUodChzYXBwbHkobCwgZXh0cmFjdF9kaWFnX3ZzX25vbl9kaWFnKSkpCiAgY29ycl9zZCA9IGFzLmRhdGEuZnJhbWUodChzYXBwbHkobCwgZXh0cmFjdF9kaWFnX3ZzX25vbl9kaWFnLGZ1bmM9c2QpKSkKICAjIHNob3J0ZW4gdGhlIHJvdyBuYW1lcwogIHJvd25hbWVzKGNvcnJfaW5mbykgPSBzYXBwbHkocm93bmFtZXMoY29ycl9pbmZvKSwKICAgICAgZnVuY3Rpb24oeClwYXN0ZShzdHJzcGxpdCh4LHNwbGl0PSIsIilbWzFdXVszOjRdLGNvbGxhcHNlPSIsIikpCiAgcm93bmFtZXMoY29ycl9zZCkgPSByb3duYW1lcyhjb3JyX2luZm8pCiAgY29ycl9pbmZvJGRhdGFzZXQgPSByb3duYW1lcyhjb3JyX2luZm8pCiAgY29ycl9zZCRkYXRhc2V0ID0gY29ycl9pbmZvJGRhdGFzZXQKICBjb3JyX2luZm8gPSBtZWx0KGNvcnJfaW5mbykKICBjb3JyX3NkID0gbWVsdChjb3JyX3NkKQogIGNvcnJfaW5mbyRzZCA9IGNvcnJfc2QkdmFsdWUKICBwcmludCgKICAgIGdncGxvdChjb3JyX2luZm8sIGFlcyh4PWRhdGFzZXQsIHk9dmFsdWUsIGZpbGw9dmFyaWFibGUpKSArCiAgICAgIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCksIHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycpICsKICAgICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj12YWx1ZS1zZCwgeW1heD12YWx1ZStzZCksbmEucm09VCwgCiAgICAgICAgICAgICAgICAgICB3aWR0aD0uMixwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSguOSkpICsKICAgIGdndGl0bGUodGFyX2RhdGFzZXQpICsgeGxhYigiVW50YXJnZXRlZCBkYXRhc2V0IikgKyB5bGFiKCJTcGVhcm1hbiIpICsKICAgICAgbGFicyhmaWxsID0gIlBhaXIgdHlwZSIpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIixsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKQogICkKfQpgYGAKCiMjIyBQbG90IHNlbGVjdGVkIGV4YW1wbGVzCgpIZXJlIGFyZSB0aGUgcmVzdWx0cyBmb3IgbGFjdGF0ZSBpbiBwbGFzbWEuCgpFeGFtcGxlIGZyb20gYSBsb2cyIGRhdGFzZXQ6CgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm1ldGFfYW5hbHlzaXNfc3RhdHMgPSAKICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMkYGxvZzIsaW1wO21lZCxsb2csaW1wYCRtZXRhX2FuYWx5c2lzX3N0YXRzCmxhY3RfcmVzID0gbWV0YV9hbmFseXNpc19zdGF0c1sKICBncmVwbCgibGFjdCIsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksaWdub3JlLmNhc2UgPSBUKSAmCiAgICBncmVwbCgicGxhc21hIixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpCl0KbGFjdF9yZXNfaG91cnMgPSBzYXBwbHkobmFtZXMobGFjdF9yZXMpLAogICAgICAgICAgICBmdW5jdGlvbih4KWFzLm51bWVyaWMoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bM10pKQpsYWN0X3JlcyA9IGxhY3RfcmVzW29yZGVyKGxhY3RfcmVzX2hvdXJzKV0KZm9yKGxhY3RfZXhhbXBsZSBpbiBuYW1lcyhsYWN0X3JlcylbMTo2XSl7CiAgY3Vycl9sYWJlbHMgPSBnc3ViKCJwbGFzbWEsIiwiIiwKICAgICAgICAgICAgICAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV1bWzFdXVssMV0pCiAgZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dJHJlX21vZGVsMSwKICAgICAgIHNsYWIgPSBjdXJyX2xhYmVscywKICAgICAgIG1haW4gPSBsYWN0X2V4YW1wbGUseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKfQoKYGBgCgpFeGFtcGxlIGZyb20gYSBzY2FsZWQgZGF0YXNldDoKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbWV0YV9hbmFseXNpc19zdGF0cyA9IAogIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cyRgaW1wLG5vbmU7aW1wLG5vbmVgJG1ldGFfYW5hbHlzaXNfc3RhdHMKbGFjdF9yZXMgPSBtZXRhX2FuYWx5c2lzX3N0YXRzWwogIGdyZXBsKCJsYWN0IixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpICYKICAgIGdyZXBsKCJwbGFzbWEiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkKXQpsYWN0X3Jlc19ob3VycyA9IHNhcHBseShuYW1lcyhsYWN0X3JlcyksCiAgICAgICAgICAgIGZ1bmN0aW9uKHgpYXMubnVtZXJpYyhzdHJzcGxpdCh4LHNwbGl0PSIsIilbWzFdXVszXSkpCmxhY3RfcmVzID0gbGFjdF9yZXNbb3JkZXIobGFjdF9yZXNfaG91cnMpXQpmb3IobGFjdF9leGFtcGxlIGluIG5hbWVzKGxhY3RfcmVzKVsxOjZdKXsKICBjdXJyX2xhYmVscyA9IGdzdWIoInBsYXNtYSwiLCIiLAogICAgICAgICAgICAgICAgICAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1tsYWN0X2V4YW1wbGVdXVtbMV1dWywxXSkKICBmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV0kcmVfbW9kZWwxLAogICAgICAgc2xhYiA9IGN1cnJfbGFiZWxzLAogICAgICAgbWFpbiA9IGxhY3RfZXhhbXBsZSx4bGFiID0gIkxvZyBmYyIsCiAgICAgICBjb2wgPSAiYmx1ZSIsY2V4ID0gMS4xKQp9CgpgYGAKCldlIGNhbiBub3cgY2hlY2sgdGhlIHNhbWUgYW5hbHlzaXMgZm9yIGxpdmVyOgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsYWN0X3JlcyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgZ3JlcGwoImxhY3QiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkgJgogICAgZ3JlcGwoImxpdmVyIixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpCl0KbGFjdF9yZXNfaG91cnMgPSBzYXBwbHkobmFtZXMobGFjdF9yZXMpLAogICAgICAgICAgICBmdW5jdGlvbih4KWFzLm51bWVyaWMoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bM10pKQpsYWN0X3JlcyA9IGxhY3RfcmVzW29yZGVyKGxhY3RfcmVzX2hvdXJzKV0KZm9yKGxhY3RfZXhhbXBsZSBpbiBuYW1lcyhsYWN0X3JlcylbMTo2XSl7CiAgY3Vycl9sYWJlbHMgPSBnc3ViKCJsaXZlcl9wb3dkZXIsIiwiIiwKICAgICAgICAgICAgICAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV1bWzFdXVssMV0pCiAgZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dJHJlX21vZGVsMSwKICAgICAgIHNsYWIgPSBjdXJyX2xhYmVscywKICAgICAgIG1haW4gPSBsYWN0X2V4YW1wbGUseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKfQoKYGBgCgoKPCEtLSBXZSBjcmVhdGUgcmVwb3J0IGJ5IHRpc3N1ZSB3aXRoIGFsbCBmb3Jlc3QgcGxvdHMgYW5kIGFuYWx5c2lzIHJlc3VsdHMuIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gbWV0YV9hbmFseXNpc190aXNzdWVzID0gc2FwcGx5KG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeClzdHJzcGxpdCh4LHNwbGl0PSIsIilbWzFdXVsyXSkgLS0+CjwhLS0gbWV0YV9hbmFseXNpc19tZXRhYm9saXRlcyA9IHNhcHBseShuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bMV0pIC0tPgoKPCEtLSBmb3IodGlzc3VlIGluIHVuaXF1ZShtZXRhX2FuYWx5c2lzX3Rpc3N1ZXMpKXsgLS0+CjwhLS0gICBwZGYocGFzdGUoIn4vRGVza3RvcC8iLHRpc3N1ZSwiLnBkZiIsc2VwPSIiKSkgLS0+CjwhLS0gfSAtLT4KCjwhLS0gYGBgIC0tPgoKCgoKRnJvbSB0aGUgcGxvdHMgYWJvdmUgd2UgdGFrZSB0aGUgbW9zdCBleHRyZW1lIGV4YW1wbGVzIGFuZCBleGFtaW5lIHRoZWlyIGZvcmVzdCBwbG90cy4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbWV0YV9hbmFseXNpc19zdGF0cyA9IAogIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cyRgaW1wLG5vbmU7aW1wLG5vbmVgJG1ldGFfYW5hbHlzaXNfc3RhdHMKCiMgUC12YWx1ZSBmb3IgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0YXJnZXRlZCBhbmQgdW50YXJnZXRlZAp0YXJnZXRlZF9kaWZmX3AgPSAKICBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWwyJHB2YWxbMl0pCgojIFAtdmFsdWVzIC0gdGFyZ2V0ZWQgdnMuIHVudGFyZ2V0ZWQKcHZhbHNfdGFyID0gc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsX3RhciRwdmFsKQpwdmFsc191bnRhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF91bnRhciRwdmFsKQpwdmFsc191bnRhciA9IHVubGlzdChwdmFsc191bnRhcltzYXBwbHkocHZhbHNfdW50YXIsbGVuZ3RoKT4wXSkKCgphZ3JlZV9leGFtcGxlID0gbmFtZXMoc2FtcGxlKHdoaWNoKHB2YWxzX3RhcjwgMWUtNSAmIHB2YWxzX3VudGFyIDwgMWUtNSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRlZF9kaWZmX3AgPiAwLjEpKVsxXSkKc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3Q8LWZ1bmN0aW9uKHMpewogIHMgPSBnc3ViKCIsdW50YXJnZXRlZCIsIiIscykKICB0aXNzdWUgPSBzdHJzcGxpdChzLHNwbGl0PSIsIilbWzFdXVsxXQogIHMgPSBnc3ViKHBhc3RlKHRpc3N1ZSwiLCIsc2VwPSIiKSwiIixzKQogIHJldHVybihzKQp9CmZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1thZ3JlZV9leGFtcGxlXV0kcmVfbW9kZWwxLAogIHNsYWIgPSBzaW1wbGlmeV9sYWJlbHNfZm9yX2ZvcmVzdCgKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2FncmVlX2V4YW1wbGVdXVtbMV1dWywxXSksCiAgbWFpbiA9IHBhc3RlKGFncmVlX2V4YW1wbGUsInNpZ25pZmljYW50IGluIGJvdGgsIHRhciBhbmQgdW50YXIgYWdyZWUiLHNlcD0iXG4iKSwKICB4bGFiID0gIkxvZyBmYyIsY29sID0gImJsdWUiKQoKYWdyZWVfcF9kaXNhZ3JlZV9iZXRhID0gbmFtZXMoc2FtcGxlKHdoaWNoKHB2YWxzX3RhcjwgMWUtNSAmIHB2YWxzX3VudGFyIDwgMWUtNSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRlZF9kaWZmX3AgPCAwLjAwMSkpWzFdKQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbYWdyZWVfcF9kaXNhZ3JlZV9iZXRhXV0kcmVfbW9kZWwxLAogIHNsYWIgPSBzaW1wbGlmeV9sYWJlbHNfZm9yX2ZvcmVzdCgKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2FncmVlX3BfZGlzYWdyZWVfYmV0YV1dW1sxXV1bLDFdKSwKICBtYWluID0gcGFzdGUoYWdyZWVfcF9kaXNhZ3JlZV9iZXRhLAogICAgICAgICAgICAgICAic2lnbmlmaWNhbnQgaW4gYm90aCwgdGFyIGFuZCB1bnRhciBkaXNhZ3JlZSIsc2VwPSJcbiIpLAogIHhsYWIgPSAiTG9nIGZjIixjb2wgPSAiYmx1ZSIpCgpkaXNhZ3JlZV9leGFtcGxlMSA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXI8IDFlLTEwICYgcHZhbHNfdW50YXIgPjAuMSkpWzFdKQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbZGlzYWdyZWVfZXhhbXBsZTFdXSRyZV9tb2RlbDEsCiAgc2xhYiA9IHNpbXBsaWZ5X2xhYmVsc19mb3JfZm9yZXN0KAogICAgbWV0YV9hbmFseXNpc19zdGF0c1tbZGlzYWdyZWVfZXhhbXBsZTFdXVtbMV1dWywxXSksCiAgbWFpbiA9IHBhc3RlKGRpc2FncmVlX2V4YW1wbGUxLAogICAgICAgICAgICAgICAic2lnbmlmaWNhbnQgdGFyZ2V0ZWQsIHRhciBhbmQgdW50YXIgZGlzYWdyZWUiLHNlcD0iXG4iKSwKICB4bGFiID0gIkxvZyBmYyIsY29sID0gImJsdWUiKQoKCmRpc2FncmVlX2V4YW1wbGUyID0gbmFtZXMoc2FtcGxlKHdoaWNoKHB2YWxzX3RhciA+IDAuMSAmIHB2YWxzX3VudGFyIDwgMWUtMjApKVsxXSkKZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUyXV0kcmVfbW9kZWwxLAogIHNsYWIgPSBzaW1wbGlmeV9sYWJlbHNfZm9yX2ZvcmVzdCgKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUyXV1bWzFdXVssMV0pLAogIG1haW4gPSBwYXN0ZShkaXNhZ3JlZV9leGFtcGxlMiwKICAgICAgICAgICAgICAgInNpZ25pZmljYW50IGluIHVudGFyZ2V0ZWQsIHRhciBhbmQgdW50YXIgZGlzYWdyZWUiLHNlcD0iXG4iKSwKICB4bGFiID0gIkxvZyBmYyIsY29sID0gImJsdWUiKQoKYGBgCgoKIyBUYXJnZXRlZCB2cy4gdW50YXJnZXRlZDogY29tcGFyaXNvbiBhcyBhIHByZWRpY3Rpb24gdGFzawoKVXNlIDUtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIGZvciBhbmFseXNpcyB3aXRoaW4gdGlzc3Vlcy4gRm9yIGVhY2ggcGFpciBvZiB0YXJnZXRlZCBhbmQgdW50YXJnZXRlZCBkYXRhc2V0cyBmcm9tIHRoZSBzYW1lIHRpc3N1ZSwgd2UgdXNlIHRoZSB1bnRhcmdldGVkIGRhdGEgYXMgdGhlIHByZWRpY3RpdmUgZmVhdHVyZXMgYW5kIGFsbCBtZXRhYm9saXRlcyBpbiB0aGUgdGFyZ2V0ZWQgZGF0YXNldHMgYXMgdGhlIGRlcGVuZGVudCB2YXJpYWJsZXMuIFRoZSBjb2RlIGJlbG93IHVzZXMgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIHJhbmRvbSBmb3Jlc3RzIHRvIHRyYWluIHRoZSBwcmVkaWN0aXZlIG1vZGVscy4gCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxldmFsPUZBTFNFLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbmZvbGRzID0gNQpwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMgPSBsaXN0KCkKZm9yKG5uMSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgbm4xX3Rpc3N1ZSA9IHN0cnNwbGl0KG5uMSxzcGxpdD0iLCIpW1sxXV1bMV0KICBubjFfdGlzc3VlID0gZ3N1YigiX3Bvd2RlciIsIiIsbm4xX3Rpc3N1ZSkKICBpZihncmVwbCgidW50YXJnZXRlZCIsbm4xKSl7bmV4dH0KICBmb3Iobm4yIGluIG5hbWVzKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMpKXsKICAgIGlmKG5uMiA9PSBubjEpe25leHR9CiAgICBpZighZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMikpe25leHR9CiAgICBubjJfdGlzc3VlID0gc3Ryc3BsaXQobm4yLHNwbGl0PSIsIilbWzFdXVsxXQogICAgbm4yX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMl90aXNzdWUpCiAgICBubjJfZGF0YXNldCA9IHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMl0KICAgIGlmKG5uMV90aXNzdWUhPW5uMl90aXNzdWUpe25leHR9CiAgICBwcmludChwYXN0ZSgiZmVhdHVyZXMgZnJvbToiLG5uMikpCiAgICBwcmludChwYXN0ZSgibGFiZWxzIGZyb206IixubjEpKQogICAgIyBnZXQgdGhlIG51bWVyaWMgZGF0YXNldHMgYW5kIHRoZWlyIGFubm90YXRpb24KICAgIHkgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogICAgeCA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJG5vcm1hbGl6ZWRfZGF0YVtbMV1dCiAgICAjIGFsaWduIHRoZSBzYW1wbGUgc2V0cwogICAgYmlkX3kgPSBtZXJnZWRfZG1hcWNfZGF0YVtjb2xuYW1lcyh5KSwiYmlkIl0KICAgIGJpZF94ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeCksImJpZCJdICAgIAogICAgIyBzdGVwIDE6IG1lcmdlIHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBCSUQKICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3gpKSE9bGVuZ3RoKGJpZF94KSl7CiAgICAgIHggPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh4LGJpZF94KQogICAgfQogICAgZWxzZXsKICAgICAgY29sbmFtZXMoeCkgPSBiaWRfeAogICAgfQogICAgaWYobGVuZ3RoKHVuaXF1ZShiaWRfeSkpIT1sZW5ndGgoYmlkX3kpKXsKICAgICAgeSA9IGFnZ3JlZ2F0ZV9yZXBlYXRlZF9zYW1wbGVzKHksYmlkX3kpCiAgICB9ZWxzZXsKICAgICAgY29sbmFtZXMoeSkgPSBiaWRfeQogICAgfQogICAgIyBzdGVwIDI6IHVzZSB0aGUgc2hhcmVkIGJpbyBpZHMKICAgIHNoYXJlZF9iaWRzID0gYXMuY2hhcmFjdGVyKGludGVyc2VjdChjb2xuYW1lcyh5KSxjb2xuYW1lcyh4KSkpCiAgICB4ID0gdChhcy5tYXRyaXgoeFssc2hhcmVkX2JpZHNdKSkKICAgIHkgPSB0KGFzLm1hdHJpeCh5WyxzaGFyZWRfYmlkc10pKQogICAgIyBBdCB0aGlzIHBvaW50IHggYW5kIHkgYXJlIG92ZXIgdGhlIHNhbWUgQklEcywgbm93IHdlIGFkZCB0aGUgbWV0YWRhdGEKICAgIHlfbWV0YSA9IHVuaXF1ZShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRzYW1wbGVfbWV0YV9wYXJzZWQpCiAgICByb3duYW1lcyh5X21ldGEpID0geV9tZXRhJGJpZAogICAgeV9tZXRhID0geV9tZXRhW3NoYXJlZF9iaWRzLF0KICAgIAogICAgIyB0YWtlIHRoZSBjb3ZhcmlhdGVzIChpZ25vcmUgZGlzdGFuY2VzKQogICAgY3Vycl9jb3ZfY29scyA9IGludGVyc2VjdChjb2xuYW1lcyh5X21ldGEpLGJpb3NwZWNfY29sc1syXSkKICAgIGN1cnJfY292cyA9IGRhdGEuZnJhbWUoeV9tZXRhWyxjdXJyX2Nvdl9jb2xzXSkKICAgIG5hbWVzKGN1cnJfY292cykgPSBjdXJyX2Nvdl9jb2xzCiAgICBjdXJyX2NvdnMkc2V4ID0geV9tZXRhJGFuaW1hbC5yZWdpc3RyYXRpb24uc2V4ICMgYWRkIHNleAogICAgIyBhZGQgdGhlIGNvdmFyaWF0ZXMgaW50byB4CiAgICB4ID0gY2JpbmQoeCxjdXJyX2NvdnMpCiAgICAKICAgICMgUnVuIHRoZSByZWdyZXNzaW9ucwogICAgZm9sZHMgPSBzYW1wbGUocmVwKDE6bmZvbGRzLCgxK25yb3coeCkvbmZvbGRzKSkpWzE6bnJvdyh4KV0KICAgIG51bUZlYXR1cmVzID0gbWluKG5jb2woeCksMjAwMCkKICAgIHByZWRzID0gYygpO3JlYWw9YygpCiAgICBmb3IoaSBpbiAxOm5jb2woeSkpewogICAgICBpZiggaSAlJSAxMCA9PSAwKXtwcmludChwYXN0ZSgiYW5hbHl6aW5nIG1ldGFib2xpdGUgbnVtYmVyOiIsaSkpfQogICAgICB5X2kgPSB5WywxXQogICAgICBpX3ByZWRzID0gYygpO2lfcmVhbD1jKCkKICAgICAgZm9yKGogaW4gMTpuZm9sZHMpewogICAgICAgIHRyX3ggPSB4W2ZvbGRzIT1qLF0KICAgICAgICB0cl95aSA9IHlfaVtmb2xkcyE9al0KICAgICAgICB0ZV94ID0geFtmb2xkcz09aixdCiAgICAgICAgdGVfeSA9IHlfaVtmb2xkcz09al0KICAgICAgICAjIHJhbmRvbSBmb3Jlc3QKICAgICAgICAjIG1vZGVsID0gcmFuZG9tRm9yZXN0KHRyX3lpLHg9dHJfeCxudHJlZSA9IDIwKQogICAgICAgICMgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICAgIG1vZGVsID0gZmVhdHVyZV9zZWxlY3Rpb25fd3JhcHBlcih0cl94LHRyX3lpLAogICAgICAgICAgICAgICAgICAgY29lZmZfb2ZfdmFyLHJhbmRvbUZvcmVzdCwKICAgICAgICAgICAgICAgICAgIHRvcEsgPSBudW1GZWF0dXJlcyxudHJlZT01MCkKICAgICAgICB0ZV9wcmVkcyA9IHByZWRpY3QobW9kZWwsbmV3ZGF0YSA9IHRlX3gpCiAgICAgICAgaV9wcmVkcyA9IGMoaV9wcmVkcyx0ZV9wcmVkcykKICAgICAgICBpX3JlYWwgPSBjKGlfcmVhbCx0ZV95KQogICAgICB9CiAgICAgIHByZWRzID0gY2JpbmQocHJlZHMsaV9wcmVkcykKICAgICAgcmVhbCA9IGNiaW5kKHJlYWwsaV9yZWFsKQogICAgfQogICAgY29sbmFtZXMocHJlZHMpID0gY29sbmFtZXMoeSkKICAgIGNvbG5hbWVzKHJlYWxzKSA9IGNvbG5hbWVzKHkpCiAgICBjdXJybmFtZSA9IHBhc3RlKG5uMSxubjIsc2VwPSI7IikKICAgIHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0c1tbY3Vycm5hbWVdXSA9IGxpc3QoCiAgICAgIHByZWRzID0gcHJlZHMscmVhbD1yZWFsCiAgICApCiAgfQp9CnNhdmVfdG9fYnVja2V0KHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cywKICAgICAgICAgICAgICAgZmlsZT0idGFyX3ZzX3VudGFyX3ByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cy5SRGF0YSIsCiAgICAgICAgICAgICAgIGJ1Y2tldCA9ICJnczovL2JpY19kYXRhX2FuYWx5c2lzL3Bhc3MxYS9tZXRhYm9sb21pY3MvIikKYGBgCgpXZSBub3cgdGFrZSB0aGUgcHJlZGljdGVkIGFuZCByZWFsIHZhbHVlcyBhbmQgZXN0aW1hdGUgdGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgaW4gZGlmZmVyZW50IHdheXMuIAoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CgpwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMgPSAgbG9hZF9mcm9tX2J1Y2tldCgKICAidGFyX3ZzX3VudGFyX3ByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cy5SRGF0YSIsCiAgICAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIsRikKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1sxXV0KCnJlc3VsdHNfbWV0cmljcyA9IGxpc3QoKQpmb3Iobm4gaW4gbmFtZXMocHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzKSl7CiAgcHJlZHMgPSBwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHNbW25uXV0kcHJlZHMKICByZWFsID0gcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubl1dJHJlYWwKICB0YXJfbmFtZSA9IHN0cnNwbGl0KG5uLHNwbGl0PSI7IilbWzFdXVsxXQogIHVudGFyX25hbWUgPSBzdHJzcGxpdChubixzcGxpdD0iOyIpW1sxXV1bMl0KICB5ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbdGFyX25hbWVdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogIGNvbG5hbWVzKHByZWRzKSA9IHJvd25hbWVzKHkpCiAgY29sbmFtZXMocmVhbCkgPSByb3duYW1lcyh5KQogIHRhcl9uYW1lID0gc2ltcGxpZnlfbWV0YWJfZGF0YXNldF9uYW1lKHRhcl9uYW1lKQogIHVudGFyX25hbWUgPSBzaW1wbGlmeV9tZXRhYl9kYXRhc2V0X25hbWUodW50YXJfbmFtZSkKICBjdXJydGlzc3VlID0gc3Ryc3BsaXQodGFyX25hbWUsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgdGFyX25hbWUgPSBnc3ViKHBhc3RlKGN1cnJ0aXNzdWUsIiwiLHNlcD0iIiksIiIsdGFyX25hbWUpCiAgdW50YXJfbmFtZSA9IGdzdWIocGFzdGUoY3VycnRpc3N1ZSwiLCIsc2VwPSIiKSwiIix1bnRhcl9uYW1lKQogIGlmKCEgY3VycnRpc3N1ZSAlaW4lIG5hbWVzKHJlc3VsdHNfbWV0cmljcykpewogICAgcmVzdWx0c19tZXRyaWNzW1tjdXJydGlzc3VlXV0gPSBsaXN0KCkKICB9CiAgaWYoISB0YXJfbmFtZSAlaW4lIG5hbWVzKHJlc3VsdHNfbWV0cmljc1tbY3VycnRpc3N1ZV1dKSl7CiAgICByZXN1bHRzX21ldHJpY3NbW2N1cnJ0aXNzdWVdXVtbdGFyX25hbWVdXSA9IGxpc3QoKQogIH0KICAKICByaG9zID0gZm9ybWF0KGRpYWcoY29yKHByZWRzLHJlYWwsbWV0aG9kPSJzcGVhcm1hbiIpKSxkaWdpdHM9MykKICByaG9zID0gYXMubnVtZXJpYyhyaG9zKQogIFNFcyA9IGNvbFN1bXMoKHByZWRzLXJlYWwpXjIpCiAgTVNFcyA9IFNFcyAvIG5yb3cocHJlZHMpCiAgUk1TRSA9IHNxcnQoTVNFcykKICByTVNFID0gTVNFcyAvIGFwcGx5KHksMSx2YXIpCiAgQ29WcyA9IGFwcGx5KHksMSxzZCkgLyBhcHBseSh5LDEsbWVhbikKICBkaXNjQ29WcyA9IGN1dChDb1ZzLGJyZWFrcyA9IDIsb3JkZXJlZF9yZXN1bHQgPSBUKQogIAogIHJlc3VsdHNfbWV0cmljc1tbY3VycnRpc3N1ZV1dW1t0YXJfbmFtZV1dW1t1bnRhcl9uYW1lXV0gPSBkYXRhLmZyYW1lKAogICAgcmhvcyxNU0VzLFJNU0Usck1TRSxDb1ZzLGRpc2NDb1ZzCiAgKQp9CgpgYGAKCldlIG5vdyBwcmVzZW50IGEgZmV3IHN1bW1hcnkgcGxvdHMuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxldmFsPUZBTFNFLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KZm9yKHRpc3N1ZSBpbiBuYW1lcyhyZXN1bHRzX21ldHJpY3MpKXsKICBmb3IodGFyIGluIG5hbWVzKHJlc3VsdHNfbWV0cmljc1tbdGlzc3VlXV0pKXsKICAgIGwgPSByZXN1bHRzX21ldHJpY3NbW3Rpc3N1ZV1dW1t0YXJdXQogICAgcmhvX3ZzX2N2ID0gYygpCiAgICBmb3IodW50YXIgaW4gbmFtZXMobCkpewogICAgICBtID0gbFtbdW50YXJdXVssYygicmhvcyIsImRpc2NDb1ZzIildICMgdGFrZSB0aGUgY3VycmVudCBtYXRyaXgKICAgICAgbSA9IGNiaW5kKHJlcCh1bnRhcixucm93KG0pKSxtKQogICAgICBtJGRpc2NDb1ZzID0gYXMubnVtZXJpYyhtJGRpc2NDb1ZzKQogICAgICByaG9fdnNfY3YgPSByYmluZChyaG9fdnNfY3YsbSkKICAgIH0KICAgIGNvbG5hbWVzKHJob192c19jdilbMV0gPSAiZGF0YXNldCIKICAgIGJveHBsb3Qocmhvc35kaXNjQ29WczpkYXRhc2V0LGRhdGE9cmhvX3ZzX2N2LGxhcz0yLAogICAgICAgICAgICB5bGFiPSJTcGVhcm1hbiIseGxhYiA9ICIiLHlsaW09YygwLDEpLAogICAgICAgICAgICBtYWluID0gcGFzdGUodGlzc3VlLHRhcixzZXA9IiwiKSkKICB9Cn0KCgpgYGAKCkFzIGFkZGl0aW9uYWwgcmVmZXJlbmNlcywgd2UgdHJhaW4gYmVsb3cgYWRkaXRpb25hbCBtb2RlbHMuIEZpcnN0LCB3ZSBjaGVjayB0aGUgcHJlZGljdGlvbiBvZiBuYWl2ZSBtb2RlbHMgdGhhdCB1c2UgdGVjaG5pY2FsIGFuZCBjbGluaWNhbCBjb3ZhcmlhdGVzIG9ubHkuIFNlY29uZCwgd2UgdXNlIG11bHRpLXRhc2sgcmVncmVzc2lvbiBhbmQgZGVlcCBsZWFybmluZyBtb2RlbHMuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxldmFsPUZBTFNFLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCmNvdl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMgPSBsaXN0KCkKZm9yKG5uMSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgbm4xX3Rpc3N1ZSA9IHN0cnNwbGl0KG5uMSxzcGxpdD0iLCIpW1sxXV1bMV0KICBubjFfdGlzc3VlID0gZ3N1YigiX3Bvd2RlciIsIiIsbm4xX3Rpc3N1ZSkKICBpZihncmVwbCgidW50YXJnZXRlZCIsbm4xKSl7bmV4dH0KICBwcmludChubjEpCiAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJG5vcm1hbGl6ZWRfZGF0YVtbMV1dCiAgeV92aWFscyA9IGNvbG5hbWVzKHkpCiAgYmlkX3kgPSBtZXJnZWRfZG1hcWNfZGF0YVtjb2xuYW1lcyh5KSwiYmlkIl0KICBjb2xuYW1lcyh5KSA9IGJpZF95CiAgeSA9IHQoYXMubWF0cml4KHkpKQogIGlmKG5jb2woeSk+MTAwMCl7bmV4dH0KICBjb3ZfY29scyA9IGMoImFuaW1hbC5yZWdpc3RyYXRpb24uc2V4IiwKICAgICAgICAgICAgICJhY3V0ZS50ZXN0LndlaWdodCIsCiAgICAgICAgICAgICAiYWN1dGUudGVzdC5kaXN0YW5jZSIsCiAgICAgICAgICAgICAiYW5pbWFsLmtleS50aW1lcG9pbnQiKQogIGNvdnMgPSBtZXJnZWRfZG1hcWNfZGF0YVt5X3ZpYWxzLGNvdl9jb2xzXQogIHggPSBjb3ZzCiAgCiAgIyBSdW4gdGhlIHJlZ3Jlc3Npb25zCiAgZm9sZHMgPSBzYW1wbGUocmVwKDE6bmZvbGRzLCgxK25yb3coeCkvbmZvbGRzKSkpWzE6bnJvdyh4KV0KICBudW1GZWF0dXJlcyA9IG1pbihuY29sKHgpLDIwMDApCiAgcHJlZHMgPSBjKCk7cmVhbD1jKCkKICBmb3IoaSBpbiAxOm5jb2woeSkpewogICAgeV9pID0geVssMV0KICAgIGlfcHJlZHMgPSBjKCk7aV9yZWFsPWMoKQogICAgZm9yKGogaW4gMTpuZm9sZHMpewogICAgICBwcmludChqKQogICAgICB0cl94ID0geFtmb2xkcyE9aixdCiAgICAgIHRyX3lpID0geV9pW2ZvbGRzIT1qXQogICAgICB0ZV94ID0geFtmb2xkcz09aixdCiAgICAgIHRlX3kgPSB5X2lbZm9sZHM9PWpdCiAgICAgICMgcmFuZG9tIGZvcmVzdAogICAgICBtb2RlbCA9IHJhbmRvbUZvcmVzdCh0cl95aSx4PXRyX3gsbnRyZWUgPSAyMCkKICAgICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICBpX3ByZWRzID0gYyhpX3ByZWRzLHRlX3ByZWRzKQogICAgICBpX3JlYWwgPSBjKGlfcmVhbCx0ZV95KQogICAgfQogICAgcHJlZHMgPSBjYmluZChwcmVkcyxpX3ByZWRzKQogICAgcmVhbCA9IGNiaW5kKHJlYWwsaV9yZWFsKQogIH0KICBjb3ZfcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubjFdXSA9IGxpc3QoCiAgICAgIHByZWRzID0gcHJlZHMscmVhbD1yZWFsCiAgICApCn0KCiMgcHJlZHMgPSBjKCk7cmVhbD1jKCkKIyBmb3IoaiBpbiAxOm5mb2xkcyl7CiMgICB0cl94ID0geFtmb2xkcyE9aixdCiMgICB0cl95ID0geVtmb2xkcyE9aixdCiMgICB0ZV94ID0geFtmb2xkcz09aixdCiMgICB0ZV95ID0geVtmb2xkcz09aixdCiMgICBtb2RlbCA9IE1UTF93cmFwcGVyKHRyX3gsdHJfeSx0eXBlPSJSZWdyZXNzaW9uIiwgUmVndWxhcml6YXRpb249IkwyMSIpCiMgICB0ZV9wcmVkcyA9IHByZWRpY3QobW9kZWwsdGVfeCkKIyAgIHJlYWwgPSByYmluZChyZWFsLHRlX3kpCiMgICBwcmVkcyA9IHJiaW5kKHByZWRzLHRlX3ByZWRzKQojIH0KIyBkaWFnKGNvcihwcmVkcyxyZWFsKSkKCiMgVXNpbmcgUExTIHJlZ3Jlc3Npb24KIyBsaWJyYXJ5KHBscykKIyBwbHNfbW9kZWwgPSBwbHNyKHl+eCxuY29tcCA9IDUsdmFsaWRhdGlvbj0iTE9PIikKIyBldmFsID0gTVNFUChwbHNfbW9kZWwpCiMgCiMgeV9wY2EgPSBwcmNvbXAoeSkKIyBwbG90KHlfcGNhKQojIGV4cGxhaW5lZF92YXIgPSB5X3BjYSRzZGV2XjIvc3VtKHlfcGNhJHNkZXZeMikKIyB5X3BjYV9tYXRyaXggPSB5X3BjYSR4WywxOjEwXQojIAojICMgcmVncmVzcyBvdXQgc2V4LCB3ZWlnaHQKIyAKIyBnZXRfZXhwbGFpbmVkX3ZhcmlhbmNlX3VzaW5nX1BDQSh4LHkpCiMgeCA9IGFwcGx5KHgsMixyZWdyZXNzX291dCxjb3ZzPWNvdnMpCiMgeSA9IGFwcGx5KHksMixyZWdyZXNzX291dCxjb3ZzPWNvdnMpCiMgZ2V0X2V4cGxhaW5lZF92YXJpYW5jZV91c2luZ19QQ0EoeCx5KQoKCmBgYAoKCgoKCgoKCgoKCgoK